Créer un kit d'outils
-
Ce tutoriel est également disponible en vidéo.
Sommaire
Introduction
Dans ce tutoriel nous allons créer 5 items qui vont être des outils et une épée. Nous allons ensuite leur ajouter des effets. Tout comme les armures, les outils ne peuvent pas être mis en metadata car ils les utilisent déjà pour l’usure de l’item.
Pré-requis
Code
La classe principale :
Commencez par déclarer dans la classe principale les cinq items :
public static Item swordTuto, pickaxeTuto, axeTuto, shovelTuto, hoeTuto;
Il faut également déclarer l’enum tool (en dessous des items) :
public static ToolMaterial toolTuto = EnumHelper.addToolMaterial("toolTuto", 2, 854, 12.0F, 4.0F, 18);
Dans l’ordre :
- toolTuto : le nom du field
- EnumHelper.addToolMaterial : une fonction de forge pour ajouter un matériel d’outils. Pensez d’ailleurs à importer net.minecraftforge.common.util.EnumHelper et net.minecraft.item.Item.ToolMaterial.
- 2 : le niveau de récolte (harvest Level en anglais). 2 permet de détruire la même chose qu’une pioche en fer (minerai d’or, de diamant, …) 1 la même chose qu’une pioche en pierre (minerai de fer, lapis), 0 seulement la même chose que le charbon et l’or (charbon, pierre) et 3 l’obsidienne comme la pioche en diamant.
- 854 : le nombre de metadata des outils. Cela correspond au nombre total de coups -1 (donc mes outils auront 855 coups).
- 12.0F : la vitesse de récolte. Cette valeur est la même que l’or, c’est donc très rapide. (Petit rappel, ctrl + clic sur ToolMaterial pour ouvrir la classe et voir les valeurs de minecraft).
- 4.0F : les dégâts en demi-cœurs des outils. Pour l’épée, 4 sera additionné à cette valeur (donc mon épée va taper 8 demi-cœurs, soit 4 cœurs), 3 pour la hache, 2 pour la pioche, et 1 pour la pelle.
- 18 : La facilité d’enchantement, plus le nombre est élevé, plus il sera facile d’avoir des bons enchantements.
Ensuite dans la fonction init, initialisez les items :
swordTuto = new ItemTutoSword(toolTuto).setUnlocalizedName("swordTuto").setTextureName(MODID + ":sword_tutoriel"); pickaxeTuto = new ItemTutoPickaxe(toolTuto).setUnlocalizedName("pickaxeTuto").setTextureName(MODID + ":pickaxe_tutoriel"); axeTuto = new ItemTutoAxe(toolTuto).setUnlocalizedName("axeTuto").setTextureName(MODID + ":axe_tutoriel"); shovelTuto = new ItemTutoShovel(toolTuto).setUnlocalizedName("shovelTuto").setTextureName(MODID + ":shovel_tutoriel"); hoeTuto = new ItemTutoHoe(toolTuto).setUnlocalizedName("hoeTuto").setTextureName(MODID + ":hoe_tutoriel");
Il s’agit du même code qu’un item basique, il y a juste en plus le field du matériel dans le constructeur.
Il ne reste plus qu’à enregistrer les items :GameRegistry.registerItem(swordTuto, "item_tuto_sword"); GameRegistry.registerItem(pickaxeTuto, "item_tuto_pickaxe"); GameRegistry.registerItem(axeTuto, "item_tuto_axe"); GameRegistry.registerItem(shovelTuto, "item_tuto_shovel"); GameRegistry.registerItem(hoeTuto, "item_tuto_hoe");
Mais aussi, n’oubliez pas d’ajouter les textures et les noms dans les fichiers de lang. Je ne vais pas revenir aux bases dans ce tutoriel, si vous ne savez plus comment faire, retournez voir les pré-requis.
La classe des outils :
Créez les cinq classes, en fonction de l’outil, changez la superClass. ItemSword pour l’épée, ItemPickaxe pour la pioche, ItemAxe pour la hache, ItemSpade (et non Shovel pour une raison inconnue) pour la pelle et ItemHoe pour la houe.
Une fois les cinq classes créées, il faut leur mettre un constructeur (passer simplement la souris sur l’erreur, et faites “add constructor NomDeLaClasse(‘ToolMaterial’)”). Maintenant que les cinq classes sont faites avec leurs constructeurs, vous ne devriez plus avoir d’erreurs et tout devrait fonctionner. Voici à quoi devrait ressembler la classe de votre épée (les autres sont identiques, seul l’extends change).package fr.minecraftforgefrance.tutoriel.common; import net.minecraft.item.ItemSword; public class ItemTutoSword extends ItemSword { public ItemTutoSword(ToolMaterial material) { super(material); } }
Rendre les outils réparables :
Même principe que pour l’armure, il suffit d’ajouter cette fonction dans chaque classe de vos outils :
public boolean getIsRepairable(ItemStack input, ItemStack repair) { if(repair.getItem() == ModTutoriel.itemTutoriel) { return true; } return false; }
ModTutoriel.itemTutoriel correspond au field de l’item (donc ClassePrincipale.nom_du_field) avec lequel je fais pouvoir réparer l’outil. Si vous souhaitez le rendre réparable avec un bloc, remplacez ClassePrincipale.nom_du_field par Item.getItemFromBlock(ClassePrincipale.nom_du_field_du_bloc).
Bonus
Finir ce tutoriel dès ici serait trop simple, nous allons ajouter un effet à l’épée puis à la hache. Comme pour les armures, ce ne sont que des exemples pour montrer ce qu’on peut faire, les exemples ne correspondront sûrement pas à ce que vous souhaitez faire, en revanche votre imagination et votre logique vous permettront de faire ce que vous souhaitez en vous inspirant de mes exemples. Il est toujours mieux de regarder le tutoriel en vidéo où l’on voit clairement comment j’ai élaboré ce code.
Un exemple d’effet sur l’épée :
Nous allons ajouter 3 modes à notre épée, le mode pourra être changé à l’aide d’un clic droit tout en ayant l’épée en main et en étant accroupi. Le premier mode (id 0) permettra d’enflammer tous les joueurs sur un rayon de 8x8 autour de l’entité tapée, le second mode (id 1) la même chose mais avec les monstres, et le dernier mode (id 2) avec les animaux.
La première étape consiste à chercher les fonctions intéressantes dans ItemSword.java et Item.java. Il existe une fonction onItemRightClick qui est appelée dès que le joueur fait un clic droit avec cette item en main. Parfait, nous allons en avoir besoin pour changer le mode de l’épée. Et il existe aussi la fonction hitEntity qui est appelé lorsqu’on tape quelque chose avec cette épée. Nous avons nos deux fonctions.
En premier nous allons nous occuper du changement du mode. Dans la classe de notre épée, ajoutons la fonction pour le clic droit :public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) { return stack; }
(Ces fonctions ne se trouvent pas par magie, elles sont dans Item.java et/ou dans ItemSword.java (en gros toutes les classes dont votre item est extends)).
Comme dit plus haut, je souhaite changer le mode uniquement si le joueur est accroupi (pour permettre quand même de se protéger avec l’épée, ce que j’ai d’ailleurs oublié de faire dans la vidéo). Le mode va être enregistré dans le tagNBT de l’ItemStack, le tagNBT d’un ItemStack étant null par défaut, il faudra vérifier qu’il en a bien un, et si non, lui en ajouter un. Ensuite on va obtenir la valeur du mode, l’augmenter. Si elle est supérieure à 2 ou égale à 3 if(mode > 2 )ou alors (if mode == 3) (les deux fonctionnent) lorsqu’on l’augmente, on la remet à 0. On envoie un message au joueur pour lui dire la valeur du mode (à faire en serveur seulement, sinon le message est en double). En revanche, si le joueur n’est pas accroupi, on fait le code dans la classe mère, donc l’épée se mettra en protection (comme le clic droit d’une épée normale).
Voila le résultat de ce que je veux faire écrit en français, compréhensible par la plupart des humains (après avoir relu 3/4 fois). Maintenant il faut traduire tout ça en code java :public ItemStack onItemRightClick(ItemStack stack, World world, EntityPlayer player) { if(player.isSneaking()) { if(!stack.hasTagCompound()) { stack.setTagCompound(new NBTTagCompound()); } byte mode = stack.getTagCompound().getByte("mode"); mode++; if(mode == 3) { mode = 0; } stack.getTagCompound().setByte("mode", mode); if(!world.isRemote) { player.addChatMessage(new ChatComponentTranslation(mode == 0 ? "sword.mode.message.0" : mode == 1 ? "sword.mode.message.1" : "sword.mode.message.2")); } } else { super.onItemRightClick(stack, world, player); } return stack; }
Et voilà ma fonction onItemRightClick une fois finie. Notez que pour le message du joueur, j’ai utilisé ChatComponentTranslation. En fonction du mode actuel, j’envoie au joueur soit sword.mode.message.0, soit sword.mode.message.1, soit sword.mode.message.2. Et maintenant que je relis mon code, je me rends compte que j’aurai pu faire plus simple :p. En effet :
player.addChatMessage(new ChatComponentTranslation("sword.mode.message." + mode));
Fait la même chose et est bien plus simple.
Je disais donc que ce code permet de faire des messages traduits, dans mon fichier lang j’ai :sword.mode.message.0=Enflammer les joueurs sword.mode.message.1=Enflammer les monstres sword.mode.message.2=Enflammer les animaux
Ainsi le message s’adaptera à la langue du jeu.
Bon, maintenant plus compliqué, on va s’occuper de la fonction :
public boolean hitEntity(ItemStack stack, EntityLivingBase attackedLiving, EntityLivingBase attackerLiving)
Je souhaite enflammer tous les mobs d’un certain type en fonction du mode de l’épée, ça tombe bien, j’ai en paramètre stack ce qui va me permettre de récupérer le tagNBT de l’ItemStack. Il me faut aussi l’instance du monde, et les coordonnées du centre de ma sélection. Et malheureusement, pas de paramètre world, ni de coordonnées x, y, z. Mais j’ai l’instance de l’entité qui vient d’attaquer avec l’épée, et de celle qui vient d’être frappée parl’épée. Je peux donc utiliser l’instance du monde de l’entité attaquée, et ses coordonnées. En fonction du mode, je fais utiliser un sélecteur différent. Ensuite avec la liste d’entités que j’aurai obtenu, j’ai juste à les faire brûler pendant un certain temps (je vais mettre 4 secondes). En java, ça donne :
public boolean hitEntity(ItemStack stack, EntityLivingBase attackedLiving, EntityLivingBase attackerLiving) { if(!stack.hasTagCompound()) // Si le stack n'a pas de tag NBT { stack.setTagCompound(new NBTTagCompound()); // je lui en ajoute un, sinon il y a un risque de NullPointerException } IEntitySelector filter; // Je déclare un filter, il est null pour l'instant if(stack.getTagCompound().getByte("mode") == 0) // si le mode est 0 { filter = new IEntitySelector() { @Override public boolean isEntityApplicable(Entity entity) { if(entity instanceof EntityPlayer) // mon sélecteur prend tout les joueurs { return true; } return false; } }; } else if(stack.getTagCompound().getByte("mode") == 1) // si le mode est 1 { filter = new IEntitySelector() { @Override public boolean isEntityApplicable(Entity entity) { if(entity instanceof EntityMob)// mon sélecteur prend tous les monstres { return true; } return false; } }; } else // sinon, le mode est forcement 2 (pas de else if ici, sinon la JVM va croire que filter peut être null, et donc vous allez avoir une erreur plus bas { filter = new IEntitySelector() { @Override public boolean isEntityApplicable(Entity entity) { if(entity instanceof EntityAnimal) // mon sélecteur prend tous les animaux { return true; } return false; } }; } List entityTargetList = attackedLiving.worldObj.selectEntitiesWithinAABB(EntityLivingBase.class, attackedLiving.boundingBox.expand(8.0D, 2.0D, 8.0D), filter); // j'obtiens la liste de toutes les entités vivantes sur un rayon de 8 en fonction du filtre for(Object entity : entityTargetList) { EntityLivingBase living = (EntityLivingBase)entity; // Il faut donc cast EntityLivingBase à l'objet pour utiliser les méthodes qui sont dans EntityLivingBase living.setFire(4); // Met feu à l'entité pendant 4 secondes } return super.hitEntity(stack, attackedLiving, attackerLiving); // Exécute le code dans la fonction hitEntity de la classe mère (endommage l'épée)
Tout le code est commenté, comme ça vous pouvez mieux comprendre ce qu’il fait à chaque endroit. Je reviens juste sur le cast de EntityLivingBase à la fin de la fonction, dans ce cas, comme le sélecteur ne sélectionne que soit des joueurs, soit des monstres, soit des animaux, ils hérites tous de EntityLivingBase, donc je n’ai pas de risque de CastException. Mais si en remplaçait par exemple EntityLivingBase par EntityPlayer, cela fonctionnerait avec le mode joueur, mais cela crasherai avec les deux autres modes de l’épée. Faite donc attention avec les cast.
On peut encore constater un petit défaut dans le code. En effet, en mode joueur, j’obtiens la liste de tous les joueurs, et je les enflamme tous, donc moi avec ! x).
Il suffit juste d’ajouter une petite condition :if(!living.equals(attackerLiving)) // Vérifie que l'entité n'est pas celle qui a donné le coup { living.setFire(4); // Mets feu à l'entité pendant 4 secondes }
Et voilà, je pense que notre épée est assez améliorée, on passe à la suite !
Un exemple d’effet sur la hache :
Pour la hache, un truc assez classique, nous allons faire que tout le tronc se détruit lorsqu’on casse la bûche du bas. Au début je voulais partir sur une boucle while qui me semblait plus approprié, mais sans succès, je suis donc partie sur une boucle for. Il faut d’abord trouver une fonction appropriée qui est déclenchée une fois le bloc détruit. La fonction :
public boolean onBlockDestroyed(ItemStack stack, World world, Block block, int x, int y, int z, EntityLivingBase living)
correspond à ce dont nous avons besoin.
Comme avant, commençons par le faire en langage humain. À partir de la coordonnée y où le bloc a été détruit, il faut vérifier tous les blocs au dessus. Il faut donc faire un boucle for qui commence à y, qui se termine à 256 (limite de la map de minecraft) et qui a un pas de 1.
Dans cette boucle for, si le bloc est du bois, alors on fait apparaître une entité item qui contient le bois, puis on met de l’air à la place du bloc. Sinon, si le bloc c’est pas du bois, on sort de la fonction. Ce qui donne :public boolean onBlockDestroyed(ItemStack stack, World world, Block block, int x, int y, int z, EntityLivingBase living) { for(int i = y; i < 256; i++) { if(world.getBlock(x, i, z).isWood(world, x, i, z)) { stack.damageItem(1, living); // endommage l'item à chaque buche cassée if(!world.isRemote && world.getGameRules().getGameRuleBooleanValue("doTileDrops")) // si la règle de jeu drop est activé { float f = 0.7F; double d0 = (double)(world.rand.nextFloat() * f) + (double)(1.0F - f) * 0.5D; double d1 = (double)(world.rand.nextFloat() * f) + (double)(1.0F - f) * 0.5D; double d2 = (double)(world.rand.nextFloat() * f) + (double)(1.0F - f) * 0.5D; EntityItem entityitem = new EntityItem(world, (double)x + d0, (double)i + d1, (double)z + d2, new ItemStack(world.getBlock(x, i, z), 1, world.getBlockMetadata(x, i, z))); // instancie une nouvelle entité item avec les coordonnées + l'id et le metadata du bois entityitem.delayBeforeCanPickup = 10; world.spawnEntityInWorld(entityitem); // spawn l'entité item } world.setBlockToAir(x, i, z); // met de l'air à la place du bloc } else { return super.onBlockDestroyed(stack, world, block, x, i, z, living); } } return super.onBlockDestroyed(stack, world, block, x, y, z, living); }
Et voilà, maintenant lorsqu’on casse une bûche avec la hache, toutes les bûches au dessus cassent avec.
Résultat
Voir le commit sur github
Le commit sur github montre clairement où ont été placés les fichiers, ainsi que ce qui a été ajouté et retiré dans le fichier.Crédits
Rédaction :
Correction :
Ce tutoriel de 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 -
Pourrait-tu insérer comment mettre les crafts s’il te plait ?
-
https://www.minecraftforgefrance.fr/topic/64/les-recettes
C’est presque la même chose en 1.7, il faut juste utilise Blocks. à la place de Block. pour les blocs de minecraft et Items à la place de Items. pour les items. de minecraft. -
Salut, j’ai tout noté comme il faut mais j’ai une erreur sur EnumHelper dans la ligne :
public static ToolMaterial toolOranite = EnumHelper.addToolMaterial("toolOranite", 3, 600, 5.0F, 2.0F, 14);
Merci d’avnace pour votre aide
-
Il faut faire les import aussi.
-
Merci c’est bon enfaite il n’y avait pas import quand je passais ma souris dessus donc j’ai fait ctrl+shift+o ! Merci a vous
-
La fonction de la hache (avec les bûches) est intéressante mais vous avez un moyen pour la 1.6 ?
Oui je sais je dois passer en 1.7 !
-
Théoriquement c’est presque la même chose, faut juste remplace world.getBlock(x, i, z).isWood(world, x, i, z) par Block.blockList[world.getBlockId(x, i, z)].isWood(world, x, i, z)
-
Oui, mais j’ai une jolie erreur
-
La méthode a surement un autre nom, regarde dans ItemTool.java et dans Item.java, moi j’ai pas le temps pour faire des supports pour les versions obsolètes.
-
Bonjour je cherche depuis ce matin et je m’arrache les yeux en cherchant comment créer un arc plus rapide a viser et qui tire un autre item que les flèches. Merci d’avance .
-
Post plutôt dans la section support pour les moddeurs.
-
bonjour j’aimerai créer une pioche avec un craft custom , pas avec un seul matériaux quoi , mais sans créer en meme temps la hache , l’épée etc . Comment faire ?
-
Il suffit de créer seulement la classe de l’épée.
-
Merci et puis j’ai un problème je ne peut pas utiliser l’item précédemment crée en suivant tes tuto . A tu une solution ?
-
Heu comment ça ? Tu peux détailler ? Et envoyer ton code aussi.
-
package com.mathiasetampes.newcraft.common; import net.minecraft.creativetab.CreativeTabs; import net.minecraft.init.Items; import net.minecraft.item.Item; import net.minecraft.item.Item.ToolMaterial; import net.minecraft.item.ItemStack; import net.minecraftforge.common.util.EnumHelper; import com.mathiasetampes.newcraft.proxy.CommonProxy; import cpw.mods.fml.common.Mod; import cpw.mods.fml.common.Mod.EventHandler; import cpw.mods.fml.common.Mod.Instance; import cpw.mods.fml.common.SidedProxy; import cpw.mods.fml.common.event.FMLInitializationEvent; import cpw.mods.fml.common.event.FMLPostInitializationEvent; import cpw.mods.fml.common.event.FMLPreInitializationEvent; import cpw.mods.fml.common.registry.GameRegistry; @Mod(modid = "newcraft",name="Newcraft",version="1.0.0") public class newcraft { @Instance("newcraft") public static newcraft instance; public static Item core; public static Item firePickaxe; public static ToolMaterial FireTools = EnumHelper.addToolMaterial("FireTool", 3, 854, 12.0F, 4.0F, 18); public static final String MODID = "newcraft"; @SidedProxy(clientSide = "com.mathiasetampes.newcraft.proxy.ClientProxy", serverSide = "com.mathiasetampes.newcraft.proxy.CommonProxy") public static CommonProxy proxy; @EventHandler public void preInit(FMLPreInitializationEvent event) { core = new Core().setUnlocalizedName("Core").setCreativeTab(CreativeTabs.tabMaterials).setTextureName(MODID + ":core"); firePickaxe = new FirePickaxe(FireTools).setUnlocalizedName("FirePickaxe").setTextureName("Confusword_tutoriel.png"); GameRegistry.registerItem(core,"Core"); GameRegistry.registerItem(firePickaxe, "FirePickaxe"); GameRegistry.addShapedRecipe(new ItemStack(core, 1, 1), new Object[]{"XYX",'X',Items.blaze_powder,'Y',Items.diamond}); GameRegistry.addShapedRecipe(new ItemStack(firePickaxe,1,1),new Object[]{"XYX","Z","Z",'X',Items.diamond,'Y',newcraft.core,'Z',Items.stick}); } @EventHandler public void init(FMLInitializationEvent event) { proxy.registerRender(); } @EventHandler public void postInit(FMLPostInitializationEvent event) { } }
J’ai finalement trouvé mais un bug persiste :j’ai crée le constructor dans la classe firePickaxe mais l’erreur reste pourquoi
-
c’est bon j’ai trouvé il faut pas mettre Items.quelquechose mais MonMod.quelquechose
-
heu excuser moi c’était un bug d’affichage l’erreur n’existait pas . Par contre le craft de la pioche ne marche pas
-
ça devrait être :
GameRegistry.addShapedRecipe(new ItemStack(firePickaxe),new Object[]{“XYX”," Z “,” Z ", ‘X’, Items.diamond, ‘Y’, core, ‘Z’, Items.stick});Les espaces sont important