29 août 2016, 17:27

Sommaire

Introduction

Dans ce tutoriel, vous allez apprendre à créer un plugin pour NEI (NotEnoughItems) (si vous ne le connaisez pas, je vous invite à aller acheter une corde, l’accrocher en hauteur, faire un noeud coulant, passer votre tête à l’intérieur et enfin vous pendre xD). J’ai également fait un tutoriel sur JEI qui est un dérivé de NEI.

Ce tutoriel expliquera comment l’utiliser pour une table de craft custom (cf ce tutoriel écrit par moi-même, il y aura des références vers celui-ci dans le code, faciles à modifier), et pour un four custom (cf ce tutoriel (1.7.10) écrit par BrokenSwing, il y aura quelques modifications par rapport à la table de craft, notament les animations).

L’entièretée du code a été inspiré du code de NEI pour Minecraft vanilla, donc si vous vous posez une question, n’hésitez pas à regarder le code de NEI.

❗ Le tutoriel est assez long et quelques erreurs ou choses pas claires peuvent toujours s’être glissées, n’hésitez pas à les signaler !

Pré-requis

Avoir une table de craft, un four ou tout autre machine avec des recettes:

Avoir des connaissances solides en java, j’ai décidé de ne pas coller les classes en entier pour que vous essayez de comprendre le code, donc si vous voulez bien suivre le tutoriel et que vous ne vous y connaissez pas beaucoup en java, regardez les très bon tutoriel d’openclassrooms.com et entrainez-vous à faire des mods plus simples.

Installation de NEI dans votre workspace

Tout d’abord, pour créer un plugin NEI, vous allez avoir besoin des sources de NEI, le meilleur moyen est d’utiliser gradlew qui va télécharger le mod déobfusqué et l’ajouter dans eclipse.

Ouvrez votre fichier .gradle et ajoutez-y ceci :

repositories {
     maven { // le repo de chicken bones, celui-ci est obligatoire pour faire fonctionner les mods non-déobfusqués
       name 'CB Repo'
       url "http://chickenbones.net/maven/"
    }
}

(si le bloc “repositories” existe déjà, mettez directement le bloc maven dedans) ceci indiquera à gradlew d’utiliser le repo maven de NEI pour accéder aux téléchargements du mod, ensuite, dans le bloc “dependencies” (en bas), ajoutez

deobfCompile  "codechicken:NotEnoughItems:${mc_version}-${nei_version}:deobf"

ça indiquera à gradlew d’ajouter le mod déobfusqué au classpath d’eclipse pour accéder aux sources et avoir le mod in-game.
Remplacez les ${mcversion} et ${nei_version}" par les versions que vous souhaitez, vous pouvez aussi regarder ce tutoriel de SCAREX pour mettre ces valeurs dans un autre fichier (méthode conseillée). La version utilisée dans le tutoriel est NEI 2.0.1.134 pour Minecraft 1.9.4.

Note : Deux autres mods nécessaires à NEI, CodeChickenCore et CodeChickenLib seront automatiquement téléchargés.

Maintenant lancez la commande

gradlew eclipse

dans l’invite de commande (

.gradlew eclipse

pour linux et OSX) pour installer NEI et c’est bon, lancez eclipse et vous pourrez utiliser NEI 🙂

Code

Classe du plugin NEI :

Nous allons commencer par créer la classe qui va gérer le plugin, cette classe ne devra en aucun cas être appelée par une autre classe de votre mod, sinon il plantera si le mod NEI n’est pas chargé ; NEI se chargera lui-même de détecter votre classe.
Premièrement, créez une classe (par exemple NEITutorielPlugin)  implements IConfigureNEI.

❗ Le nom de la classe doit obligatoirement commencer par NEI et se terminer par Config (NEIVotreNomDeClasseConfig), et veillez à bien respecter la case.

Ensuite, ajoutez les fonctions que eclipse vous demandera. La première fonction, loadConfig() contiendra tout ce qu’il faut pour enregistrer les classes nécessaires au plugin, la fonction “getName” doit retourner le nom du plugin et “getVersion” sa version, ces deux fonctions sont utilisées pour la liste des mods chargés.

Code de la classe :

    package net.mff.tuto.nei;

    import net.mff.tuto.client.gui.GuiCraftingTable;
    import codechicken.nei.api.API;
    import codechicken.nei.api.IConfigureNEI;
    import codechicken.nei.recipe.DefaultOverlayHandler;

    public class NEIPluginConfig implements IConfigureNEI
    {
    @Override
    public void loadConfig()
    {

    }
    @Override
    public String getName() 
    {
    return "TutorielNEIPlugin";
    }
    @Override
    public String getVersion() 
    {
    return "1.0";
    }
    }

Handler de la recette :

Pour afficher une recette, vous allez avoir beoin d’un RecipeHandler qui contiendra toutes les données concernant la machine (texture, slots, nom, recettes), identifié avec un String unique, les catégories vanilla sont “crafting, crafting2x2, smelting, fuel, brewing”.

Créez une classe TutorielShapedRecipeHandler extends TemplateRecipeHandler et ajoutez-y les fonctions manquantes que eclipse vous demandera.

Ajoutez le champ

    public static final String INDENTIFIER = ModTutoriel.MODID + ".crafting";

qui identifiera votre handler (il doit être unique, donc laissez-y bien votre modid).

Maintenant, regardons les fonctions que nous venons d’ajouter, la permière, “getRecipeName” devra retourner le nom de la recette qui sera afficher sur le gui (utilisez “I18.format(“le string à traduire”)” pour traduire quelque chose avec votre fichier de langues), la seconde, “getGuiTexture” devra retourner la texture de votre gui sous forme de string, donc "modid:textures/gui/textureDeVotreGui.png, ou, si vous avez mis la variable “texture” de votre gui en “public static”, “GuiCraftingTable.texture.toString()”.

Ensuite, nous allons ajouter d’autres fonctions utiles, pour rappel vous pouvez faire ctrl+espac et commencer à taper le nom d’une fonction pour l’ajouter (dans eclipse), ajoutez la fonction “getGuiClass”, elle devra retourner l’objet “Class” de votre gui, donc ClasseDeVotreGui.class. De la même manière, ajoutez la fonction “getOverlayIdentifier” qui devra retourner l’object “INDENTIFIER” créé précédemment. Ajoutez la fonction “loadTransferRects” qui permettra d’ajouter une zone cliquable qui permettra d’afficher toutes les recettes du même type, insérez-y cette ligne :

    transferRects.add(new RecipeTransferRect(new Rectangle(84, 23, 24, 18), INDENTIFIER));//Vous pouvez bien sur changer la position et la taille du rectangle (les arguments sont x, y, width, height)

Maintenant les choses vont se compliquer un peu et vont être séparées par type de recette :

*Tables de craft : *

Aller directement à la partie pour les fours et autres machines

Je vous conseille de d’abord vous occuper de la ShapedRecipe, cela nous permettra de mettre extends TutorielShapedRecipeHandler à l’handler qui s’occupera des shaped recipes.

- Shaped Recipes :

Ajoutez ces trois fonctions à votre classe, vous pourrez les comprendres par vous mêmes et j’ai commenté à quoi servent les principales choses du code, elles permettent de charger les recettes contenant l’item recherché :

    @Override
    public void loadCraftingRecipes(String outputId, Object... results) 
    {
       if (outputId.equals(INDENTIFIER)) //Si on doit afficher toutes les recettes de ce type
       {
           for (IRecipe irecipe : TutorielCraftingManager.getInstance().getRecipeList()) //Pour chaque recette
           {
               CachedShapedRecipe recipe = null;
               if (irecipe instanceof TutorielShapedRecipes)
                   recipe = new CachedShapedRecipe((TutorielShapedRecipes) irecipe);
               if (recipe == null)
                   continue;
               recipe.computeVisuals();
               arecipes.add(recipe); //On l'ajoute aux recettes à afficher
           }
       }
       else
           super.loadCraftingRecipes(outputId, results); //Va charger les recettes pour un item précis
    }
    @Override
    public void loadCraftingRecipes(ItemStack result) 
    {
       for (IRecipe irecipe : TutorielCraftingManager.getInstance().getRecipeList()) //Pour chaque recette
       {
           if (NEIServerUtils.areStacksSameTypeCrafting(irecipe.getRecipeOutput(), result)) //On teste si elle correspond à celle que l'on cherche
           {
               CachedShapedRecipe recipe = null;
               if (irecipe instanceof TutorielShapedRecipes)
                   recipe = new CachedShapedRecipe((TutorielShapedRecipes) irecipe);
               if (recipe == null)
                   continue;
               recipe.computeVisuals();
               arecipes.add(recipe); //On l'ajoute aux recettes à afficher
           }
       }
    }
    @Override
    public void loadUsageRecipes(ItemStack ingredient)
    {
       for (IRecipe irecipe : TutorielCraftingManager.getInstance().getRecipeList())  //Pour chaque recette
       {
           CachedShapedRecipe recipe = null;
           if (irecipe instanceof TutorielShapedRecipes)
            recipe = new CachedShapedRecipe((TutorielShapedRecipes) irecipe);
           if (recipe == null || !recipe.contains(recipe.ingredients, ingredient.getItem())) //Si la recette ne contient pas l'ingrédient cherché
               continue;
           recipe.computeVisuals();
           if (recipe.contains(recipe.ingredients, ingredient)) //On teste si elle correspond à celle que l'on cherche
           {
               recipe.setIngredientPermutation(recipe.ingredients, ingredient);
               arecipes.add(recipe); //On l'ajoute aux recettes à afficher
           }
       }
    }

Vous allez avoir des erreurs car il manque une classe, nous allons la mettre directement dans la classe du handler (et oui on peut mettre des classes dans des classes), voici son code commenté :

    public class CachedShapedRecipe extends CachedRecipe 
    {
       public ArrayList <positionedstack>ingredients;
       public PositionedStack result;

       public CachedShapedRecipe(int width, int height, Object[] items, ItemStack out) 
       {
           result = new PositionedStack(out, 135, 40); //135 : position x du slot sur le gui, 40 : position y
           ingredients = new ArrayList<positionedstack>();
           setIngredients(width, height, items);
       }
       public CachedShapedRecipe(TutorielShapedRecipes recipe)
       {
           this(recipe.recipeWidth, recipe.recipeHeight, recipe.recipeItems, recipe.getRecipeOutput());
       }
       /**
        * @param width
        * @param height
        * @param items  an ItemStack[] or ItemStack[][]
        */
       public void setIngredients(int width, int height, Object[] items) 
       {
           for (int x = 0; x < width; x++) //Pour chaque slot en largeur
           {
               for (int y = 0; y < height; y++) //Pour chaque slot en hauteur
               {
                Object o = items[y * width + x]; //Obtention de l'item devant être dans ce slot
                   if (o == null) //Si il n'y a pas d'item dans ce slot
                       continue; //On passe au suivant
                   if(o instanceof String) //Si c'est un string (pour l'ore dictionnary)
                    o = OreDictionary.getOres((String) o); //On prend les ores correspondant au string
                   PositionedStack stack = new PositionedStack(o, x * 18 + 1, y * 18 + 4, false); //On crée un slot pour l'item, 
                   //vous devrez probablement changer les "+ 1" et "+ 4" en fonction de la position de vos slots sur votre gui (les valeurs sont bonnes si vous avez suivi mon tuto sur les table de craft)
                   //elles correspondent à la position du premier slot moins l'offset de la portion de texture qui va être dessinée sur le gui de NEI (par défaut, 5, 11 mais nous allons le modifier après)
                   stack.setMaxSize(1);
                   ingredients.add(stack); //Et on l'ajoute
               }
           }
       }
       @Override
       public List <positionedstack>getIngredients()
       {
        //Retourne les ingrédients en fonction du temps si il y en a plusieurs
           return getCycledIngredients(cycleticks / 20, ingredients);
       }
       public PositionedStack getResult()
       {
           return result;
       }
       public void computeVisuals()
       {
        //Va générer les items semblables pour chaque items
           for (PositionedStack p : ingredients)
               p.generatePermutations();
       }
    }

Il restera un petit problème (uniquement si vous avez une taille de grille de craft différent de 3*3), la grille dessinée sur le gui sera décalée, nous allons mettre deux fonctions pour empêcher ça :

    public void drawBackground(int recipe)
    {
       GlStateManager.color(1, 1, 1, 1); //On reset la couleur
       GuiDraw.changeTexture(getGuiTexture()); //On bind la texture du gui
       GuiDraw.drawTexturedModalRect(0, 0, 7, 3, 170, 74); //On dessine la texture, arguments : x, y, position x de la portion dessinée sur la texture, position y de la portion dessinée sur la texture, largeur de la portion, hauteur de la portion
    }
    @Override
    public int recipiesPerPage() //Nombre de recettes par page
    {
       return 1; //Par défaut deux, mais notre texture est trop grande pour en mettre plusieurs par page
    }

Gardez ces valeurs si vous avez suivi mon tutoriel sur les tables de craft.

- Shapeless Recipes :

Note :  je précise que vous devez créer un autre handler pour les Shapeless Recipes, mais au lieu de l’extends TemplateRecipeHandler, mettez extends VotreHandlerPourLesShapedRecipes, puis nous allons redéfinir les fonctions de chargement des recettes, la classe que nous avons créé précédemment et le nom de la recette :

(L’explication du code sera bientôt ajoutée)

    public int[][] stackorder = new int[][] { { 0, 0 }, { 1, 0 }, { 0, 1 }, { 1, 1 }, { 0, 2 }, { 1, 2 }, { 2, 0 }, { 2, 1 }, { 2, 2 }, {0,3}, {3,0}, {1,3}, {3,1}, {2, 3}, {3,2}, {3,3}};

    public class CachedShapelessRecipe extends CachedRecipe 
    {
       public CachedShapelessRecipe(Object[] input, ItemStack output)
       {
           this(Arrays.asList(input), output);
       }
       public CachedShapelessRecipe(List input, ItemStack output)
       {
           ingredients = new ArrayList<positionedstack>();
           setIngredients(input);
           result = new PositionedStack(output, 135, 40); //135 : position x du slot sur le gui, 40 : position y
       }
       public CachedShapelessRecipe(TutorielShapelessRecipe irecipe) 
       {
        this(irecipe.recipeItems, irecipe.getRecipeOutput());
    }
    public void setIngredients(List items) 
       {
           ingredients.clear();
           for (int ingred = 0; ingred < items.size(); ingred++)
           {
               PositionedStack stack = new PositionedStack(items.get(ingred), stackorder[ingred][0] * 18 + 1, stackorder[ingred][1] * 18 + 4);
               stack.setMaxSize(1);
               ingredients.add(stack);
           }
       }
       @Override
       public List <positionedstack>getIngredients()
       {
           return getCycledIngredients(cycleticks / 20, ingredients);
       }
       @Override
       public PositionedStack getResult()
       {
           return result;
       }
       public ArrayList <positionedstack>ingredients;
       public PositionedStack result;
    }
    @Override
    public String getRecipeName() 
    {
       return "TutorielShapelessRecipe";
    }
    @Override
    public void loadCraftingRecipes(String outputId, Object... results) 
    {
       if (outputId.equals(TutorielShapedRecipeHandler.INDENTIFIER)) 
       {
           List <irecipe>allrecipes = TutorielCraftingManager.getInstance().getRecipeList();
           for (IRecipe irecipe : allrecipes) 
           {
               CachedShapelessRecipe recipe = null;
               if (irecipe instanceof TutorielShapelessRecipe)
                   recipe = new CachedShapelessRecipe((TutorielShapelessRecipe) irecipe);
               if (recipe == null)
                   continue;
               arecipes.add(recipe);
           }
       }
       else
           super.loadCraftingRecipes(outputId, results);
    }
    @Override
    public void loadCraftingRecipes(ItemStack result)
    {
       List <irecipe>allrecipes = TutorielCraftingManager.getInstance().getRecipeList();
       for (IRecipe irecipe : allrecipes) 
       {
           if (NEIServerUtils.areStacksSameTypeCrafting(irecipe.getRecipeOutput(), result)) 
           {
               CachedShapelessRecipe recipe = null;
               if (irecipe instanceof TutorielShapelessRecipe)
                   recipe = new CachedShapelessRecipe((TutorielShapelessRecipe) irecipe);
               if (recipe == null)
                   continue;
               arecipes.add(recipe);
           }
       }
    }
    @Override
    public void loadUsageRecipes(ItemStack ingredient) 
    {
       List <irecipe>allrecipes = TutorielCraftingManager.getInstance().getRecipeList();
       for (IRecipe irecipe : allrecipes) 
       {
           CachedShapelessRecipe recipe = null;
           if (irecipe instanceof TutorielShapelessRecipe)
               recipe = new CachedShapelessRecipe((TutorielShapelessRecipe) irecipe);
           if (recipe == null)
               continue;
           if (recipe.contains(recipe.ingredients, ingredient))
           {
               recipe.setIngredientPermutation(recipe.ingredients, ingredient);
               arecipes.add(recipe);
           }
       }
    }

Sauter la partie pour les fours

*Fours et autres machines : *

Cette sous partie va être divisée en deux autres parties, une pour les recettes de cuisson du four et une pour les “carburants”.

- FurnaceRecipeHandler :

Ajoutez ces trois fonctions à votre classe, vous pourrez les comprendres par vous mêmes et j’ai commenté à quoi servent les principales choses du code, elles permettent de charger les recettes contenant l’item recherché :

    @Override
    public void loadCraftingRecipes(String outputId, Object... results)
    {
       if (outputId.equals(IDENTIFIER) && getClass() == VotreClasse.class) //Si on doit afficher toutes les recettes de ce type
       {
           Map <itemstack, itemstack="">recipes = VotreClasseDeRecettesPourLeFour.instance().getSmeltingList();
           for (Entry <itemstack, itemstack="">recipe : recipes.entrySet()) //Pour toutes les recettes
               arecipes.add(new SmeltingPair(recipe.getKey(), recipe.getValue())); //On l'ajoute à celles à afficher
       } 
       else
           super.loadCraftingRecipes(outputId, results); //Va charger les recettes pour un item précis
    }

    @Override
    public void loadCraftingRecipes(ItemStack result) 
    {
       Map <itemstack, itemstack="">recipes = VotreClasseDeRecettes.instance().getSmeltingList();
       for (Entry <itemstack, itemstack="">recipe : recipes.entrySet()) //Pour chaque recette
       {
           if (NEIServerUtils.areStacksSameType(recipe.getValue(), result)) //On teste si elle correspond à celle que l'on cherche
               arecipes.add(new SmeltingPair(recipe.getKey(), recipe.getValue())); //On l'ajoute aux recettes à afficher
       }
    }

    @Override
    public void loadUsageRecipes(String inputId, Object... ingredients) 
    {
       if (inputId.equals("fuel") && getClass() == VotreClasse.class)
           loadCraftingRecipes(IDENTIFIER);
       else
           super.loadUsageRecipes(inputId, ingredients);
    }

    @Override
    public void loadUsageRecipes(ItemStack ingredient) 
    {
       Map <itemstack, itemstack="">recipes = VotreClasseDeRecettes.instance().getSmeltingList();
       for (Entry <itemstack, itemstack="">recipe : recipes.entrySet()) //Pour chaque recette
       {
           if (NEIServerUtils.areStacksSameTypeCrafting(recipe.getKey(), ingredient))  //On teste si elle correspond à celle que l'on cherche
           {
               SmeltingPair arecipe = new SmeltingPair(recipe.getKey(), recipe.getValue());
               arecipe.setIngredientPermutation(Arrays.asList(arecipe.ingred), ingredient);
               arecipes.add(arecipe); //On l'ajoute aux recettes à afficher
           }
       }
    }

Vous devriez avoir des erreurs sur “SmeltingPair”, nous allons créer la classe, je vous conseille de la mettre à l’intérieur de la classe du handler (c’est possible pour ceux qui ne le savaient pas 😉 :

    public class SmeltingPair extends CachedRecipe 
    {
           public SmeltingPair(ItemStack ingred, ItemStack result) 
    {
               ingred.stackSize = 1;
    //Slot pour les ingrédients
               this.ingred = new PositionedStack(ingred, 51, 6); //51 : position x du slot sur le gui, 6 : position y
    //Slot pour le résultat
               this.result = new PositionedStack(result, 111, 24); //111 : position x du slot sur le gui, 24 : position y
           }

           public List <positionedstack>getIngredients()
    {
               return getCycledIngredients(cycleticks / 48, Arrays.asList(ingred)); //Retourne un des ingrédients possibles en fonction du temps
           }

           public PositionedStack getResult()
    {
               return result;
           }

           public PositionedStack getOtherStack() //Retourne des stacks supplémentaires non impliqués directement dans la recette (exemple ici avec les fuels)
    {
               return afuels.get((cycleticks / 48) % afuels.size()).stack;
           }

           PositionedStack ingred;
           PositionedStack result;
       }

Vous aurez encore des erreurs (eh oui c’est pas fini), ajoutez cette liste à votre classe :

    public static ArrayList <fuelpair>afuels;

elle contiendra tous les carburants possible pour les recettes, ensuite pour que cette liste soit remplie au chargement de la classe, ajoutez ceci :

    @Override
       public TemplateRecipeHandler newInstance() 
       {
           if (afuels == null || afuels.isEmpty()) //si la liste est vide
               findFuels();
           return super.newInstance(); //Retourne une instance de la classe (appelle le constructeur)
       }
       private static Set excludedFuels() //Fuels qui ne seront pas affichés sur le gui
      {
           Set efuels = new HashSet();
           efuels.add(Item.getItemFromBlock(Blocks.BROWN_MUSHROOM));
           efuels.add(Item.getItemFromBlock(Blocks.RED_MUSHROOM));
           efuels.add(Item.getItemFromBlock(Blocks.STANDING_SIGN));
           efuels.add(Item.getItemFromBlock(Blocks.WALL_SIGN));
           efuels.add(Item.getItemFromBlock(Blocks.TRAPPED_CHEST));
           return efuels;
       }
       private static void findFuels() //Pour chaque item existant, regarde si on peu l'utiliser comme carburant, et si oui l'ajoute à la liste des carburants
       {
           afuels = new ArrayList<fuelpair>();
           Set efuels = excludedFuels();
           for (ItemStack item : ItemList.items)
          {
               Block block = Block.getBlockFromItem(item.getItem());
               if (block instanceof BlockDoor)
                   continue;
               if (efuels.contains(item.getItem()))
                   continue;
               int burnTime = TileEntityDeVotreFour.getItemBurnTime(item);
               if (burnTime > 0) {
                   afuels.add(new FuelPair(item.copy(), burnTime));
               }
           }
       }

Puis, pour agrémenter le gui, vous pouvez ajouter les barres de progression de la cuisson et de la “chaleur” :

    @Override
       public void drawExtras(int recipe) 
    {
           //Arguments : position x, pos y, pos x sur la texture, pos y sur la texture, largeur, hauteur, nombre de ticks pour compléter la barre,
    direction (0 droite, 1 bas, 2 gauche, 3 haut)
           drawProgressBar(51, 25, 176, 0, 14, 14, 48, 7);
           drawProgressBar(74, 23, 176, 14, 24, 16, 48, 0);
       }

- FuelRecipesHandler :

Vous allez également avoir besoin d’un Handler pour les carburants, créez-en un comme indiqué plus haut (ici), changer l’extends par les handler des recettes du four (normalement) créé juste avant, puis ajoutez ces fonctions à votre classe, vous pourrez les comprendres par vous mêmes et j’ai commenté à quoi servent les principales choses du code, elles permettent de charger les recettes contenant l’item recherché :

    private void loadAllSmelting() 
    {
       Map <itemstack, itemstack="">recipes = FurnaceRecipes.instance().getSmeltingList();
       for (Entry <itemstack, itemstack="">recipe : recipes.entrySet())
           mfurnace.add(new SmeltingPair(recipe.getKey(), recipe.getValue()));
    }
    @Override
    public void loadCraftingRecipes(String outputId, Object... results) 
    {
       if (outputId.equals("fuel") && getClass() == LeNomDeLaClasse.class)
      {
           for (FuelPair fuel : afuels) //Pour chaque carburant
               arecipes.add(new CachedFuelRecipe(fuel)); //On l'ajoute aux recettes à afficher
       }
    }
    @Override
    public void loadUsageRecipes(ItemStack ingredient) 
    {
       for (FuelPair fuel : afuels) //Pour tous les carburants
       {
           if (fuel.stack.contains(ingredient)) //On teste si la recette contient l'item recherché
               arecipes.add(new CachedFuelRecipe(fuel)); //On l'ajoute aux recettes à afficher
       }
    }

Vous aurez quelques erreurs, nous allons les voir plus tard, ajoutez cette fonction qui affichera des informations sur le fuel :

        @Override
       public List <string>handleItemTooltip(GuiRecipe gui, ItemStack stack, List <string>currenttip, int recipe) {
           CachedFuelRecipe crecipe = (CachedFuelRecipe) arecipes.get(recipe);
           FuelPair fuel = crecipe.fuel;
           float burnTime = fuel.burnTime / 200F;

           if (gui.isMouseOver(fuel.stack, recipe) && burnTime < 1) {
               burnTime = 1F / burnTime;
               String s_time = Float.toString(burnTime);
               if (burnTime == Math.round(burnTime)) {
                   s_time = Integer.toString((int) burnTime);
               }

               currenttip.add(translate("recipe.fuel.required", s_time));
           } else if ((gui.isMouseOver(crecipe.getResult(), recipe) || gui.isMouseOver(crecipe.getIngredient(), recipe)) && burnTime > 1) {
               String s_time = Float.toString(burnTime);
               if (burnTime == Math.round(burnTime)) {
                   s_time = Integer.toString((int) burnTime);
               }

               currenttip.add(translate("recipe.fuel." + (gui.isMouseOver(crecipe.getResult(), recipe) ? "produced" : "processed"), s_time));
           }

           return currenttip;
       }

Puis rajoutez ce constructeur (en changeant bien le nom) au début de votre classe :

    public FuelRecipeHandler()
    {
       super(); //Appelle le constructeur de la classe parente (le extends)
       loadAllSmelting(); //Charge les recettes
    }

Maintenant occupons nous des erreurs qu’il nous reste, ajoutez ceci dans votre classe :

    public class CachedFuelRecipe extends CachedRecipe {
           public FuelPair fuel;

           public CachedFuelRecipe(FuelPair fuel) {
               this.fuel = fuel;
           }

           @Override
           public PositionedStack getIngredient() {
               return mfurnace.get(cycleticks / 48 % mfurnace.size()).ingred;
           }

           @Override
           public PositionedStack getResult() {
               return mfurnace.get(cycleticks / 48 % mfurnace.size()).result;
           }

           @Override
           public PositionedStack getOtherStack() {
               return fuel.stack;
           }
       }
    ///Liste des recettes de "carburant"
    private ArrayList <smeltingpair>mfurnace = new ArrayList<furnacerecipehandler.smeltingpair>();

*Note importante : *

Si il manque des parties la texture de votre gui, vous pouvez rajouter ceci dans votre handler pour agrandir la zone dessinée :

    @Override
    public void drawBackground(int recipe)
    {
       GlStateManager.color(1, 1, 1, 1); //On reset la couleur
       GuiDraw.changeTexture(getGuiTexture()); //On bind la texture du gui
       GuiDraw.drawTexturedModalRect(0, 0, textureX, textureY, Largeur par défaut 166, Hauteur par défaut 65); //On dessine la texture, arguments : x, y, position x de la portion dessinée sur la texture, position y de la portion dessinée sur la texture, largeur de la portion, hauteur de la portion
    }
    @Override
    public int recipiesPerPage() //Nombre de recettes par page
    {
       return 1; //Par défaut deux, mais notre texture est trop grande pour en mettre plusieurs par page
    }

Je vous laisse mettre les valeurs qu’il faut en fonction de votre texture 😉

Enregistrement du RecipeHandler :

Pour en finir avec le RecipeHandler, ajoutez ces deux lignes dans la fonction loadConfig de la classe de votre plugin, une qui va l’enregistrer en tant qu’handler pour voir les recettes que l’on peut faire avec un item, et une pour voir les recettes pour fabriquer un item donné :

    API.registerRecipeHandler(new TutorielShapedRecipeHandler());
    API.registerUsageHandler(new TutorielShapedRecipeHandler());

❗ N’oubliez pas de faire ceci pour tous vos handlers si vous en avez plusieurs.

Finalisation :

*Ajout d’un TransferHandler pour transférer les item directement dans l’inventaire de la machine (uniquement pour les tables de craft) : *
Ajoutez ces ligne dans la fonction register de votre plugin :

    API.registerGuiOverlay(GuiCraftingTable.class, TutorielShapedRecipeHandler.INDENTIFIER, 7, 3);
    API.registerGuiOverlayHandler(GuiCraftingTable.class, new DefaultOverlayHandler(7, 3), TutorielShapedRecipeHandler.INDENTIFIER);

Le “7” et le “3” sont les positions x et y de la portion dessinée sur le gui de NEI de la texture de la table de craft, laissez-les si vous avez suivi mon tutoriel sur les tables de craft.

Enfin, n’oubliez pas de changer toutes les références aux classes de Minecraft (pour la liste des recettes pas exemple) par des références à vos classes.

Conclusion :

Et voilà ce tutoriel très attendu est terminé, j’espère que vous n’aurez pas eu de problèmes et qu’il vous aura été utile.
Si vous avez un problème ou une suggestion quelque part, n’hésitez pas 😉

Bonus

Proposez des bonus 😉

Résultat

Coming soon 🙂

Crédits

Rédaction :

  • →AymericRed←Sur l’idée originale de moscaphone421 pour le bonus du tutoriel sur la création de table de craft.

Correction :

  • Personne 😛


Ce tutoriel d’AymericRed publié sur Minecraft Forge France est mis à disposition selon les termes de la licence Creative Commons Attribution - Pas d’Utilisation Commerciale - Partage dans les Mêmes Conditions 4.0 International

retourSommaire des tutoriels