[1.8+/1.9+/1.10+][NEI] Créer un plugin NotEnoughItems pour sa table de craft/son four



  • 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 de <votre pseudo> 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

    Retour vers le sommaire des tutoriels



  • Salut, j'aimerais savoir quels sont les changements à effectués pour la 1.7.10



  • @'Minantcraft':

    Salut, j'aimerais savoir quels sont les changements à effectués pour la 1.7.10

    Regarde déjà si y'en a, mais comme je crois l'avoir déjà dit : NEI n'est plus disponible en 1.7.10 ( Le CCL n'est plus disponible : http://chickenbones.net/maven/codechicken/CodeChickenLib/ ).

    PS : y'a un petit problème dans le tuto, c'est pas gradlew build mais gradlew eclipse (ou idea)



  • @Minantcraft Normalement il ne devrait pas y en avoir, mais comme SCAREX, le CCL n'est plus dispo en 1.7.10.
    @SCAREX effectivement petite erreur de ma part, c'est corrigé (et je vais le faire sur le tuto de JEI où j'ai du faire la même erreur).



  • Pour bonus tu peux mettre comment faire pour que, par exemple quand on appuie sur 'r' sur u item ça nous mette un texte et 'u' un autre texte
    EDIT: je comprends pas pour ccl, moi ça fonctionne toujours



  • Le texte, tu voudrais que ce soit une description de l'item, ou de la recette en question ?
    Peut-être que CCL n'est pas requis en 1.7.10…



  • NEI a besoin de CCC (CodeChickenCore) qui lui même télécharge le CCL (CodeChickenLib) dans le dossier mods (sauf s'il est déjà présent) à partir du maven. Sachant que le CCL n'est plus disponible en 1.7.10 tu ne pourra pas lancer ton jeu en revanche tu pourras compiler normalement (tant que t'utilises que les classes de NEI)



  • @'AymericRed':

    Le texte, tu voudrais que ce soit une description de l'item, ou de la recette en question ?
    Peut-être que CCL n'est pas requis en 1.7.10…

    Recette ou utilité 'u'



  • La partie four est bientôt fini ?



  • Je vais m'y remettre d'ici peut (ce soir ou demain), j'avais mis ça en pause car j'ai été assez occupé ces derniers temps, et j'en avais un peu marre de modder, je voulais faire une pause ^^
    Donc d'ici ce week-end ce sera complété (normalement).



  • D'accord merci à toi



  • Alors je suis désolé mais finalement ça sera fait plutôt demain ou après demain.



  • @'AymericRed':

    Alors je suis désolé mais finalement ça sera fait plutôt demain ou après demain.

    Ok c'est pas grave, on attendra bien. C'est pas comme ci on le faisait déjà 😉

    Envoyé de mon iPad en utilisant Tapatalk



  • Bon ben j'ai galléré tout ma soirée a corriger un gros bug qui m'empêchait d'utiliser mon pc à cause d'un p*** d'alim no name que avait fait crash mon pc, donc pas aujourd'hui 😕



  • Ok 😕

    Envoyé de mon iPad en utilisant Tapatalk



  • Vu que je suis assez occupé en ce moment, j'ai décidé d'apporter bouts par bouts le code de la partie sur le four, ce qui a été commencé 😉



  • Ok merci

    Envoyé de mon SM-G360F en utilisant Tapatalk



  • Rédaction du tutoriel (enfin) totalement terminée, j'ai juste à vérifier si je me suis pas gouré quelque part, et c'est bon 😉



  • @'AymericRed':

    Rédaction du tutoriel (enfin) totalement terminée, j'ai juste à vérifier si je me suis pas gouré quelque part, et c'est bon 😉

    Super merci beaucoup 🙂

    PS: Pour le bonus tu pourrait expliquer comment faire pour ajouter un texte au 'r' ou au 'u' pour avoir une description comme pour l'unstable ingot la description de son utilisation dangereuse. Ou en appuyant sur 'r' sur un item, un texte qui dit qu'il se trouve dans les donjons, …



  • Bonne idée, je vais voir ça