Le guide ultime des Items en 1.12 (Basiques, complexes, NBTTags, metadata/variantes)



  • Sommaire

    Introduction

    C'est parti pour un petit (gros) tutoriel sur la création d'items basiques, complexes, sur la metadata, sur les NBTTags et surtout sur l'optimisation de l'enregistrement des items, ainsi que de l'enregistrement de leur modèle. Comme vous le voyez, il y a beaucoup à faire 🙂

    :interrogation: À lire : Je ne prétends pas que ma méthode est la meilleure, bien que j'ai essayé de faire quelque chose d'optimisé en utilisant les connaissances que j'ai à disposition. Le but étant de définir une structure de base, demandant un minimum de code à rajouter lors de la personnalisation. Chaque classe a pour structure un code commenté, et des précisions supplémentaires sur certains points en-dessous. S'il vous plait, essayez de comprendre à 100% le code avant de le recopier, quitte à faire des recherches supplémentaires. Sinon vous n'apprendrez strictement rien et ce serait dommage.

    Pré-requis

    ❗ Ce tutoriel n'est pas adapté aux débutants en Java ! Notamment si :
    -> Vous n'êtes pas familier avec l'un des concepts de base du Java, vous pouvez consulter le tutoriel d'OpenClassroom.
    -> Vous n'êtes pas familier avec les nouveaux concepts de Java 8, notamment ceci

    Code

    Une classe générique pour les items du mod ☆☆☆☆☆

    Commençons par la classe "parente" des items du mod, dont le nom devrait logiquement être ItemVotreMod :

    package net.dylem.test_mod.item;
    
    import net.dylem.test_mod.TestMod;
    import net.minecraft.item.Item;
    
    /*
        * Classe générique pour les items du mod
        * Tous les items du mod le pouvant étendent cette classe
        */
    public class ItemTestMod extends Item {
    
        public ItemTestMod(final String itemName) {
    
            setItemName(this, itemName);
            setCreativeTab(TestMod.TEST_TAB); // Ou par exemple setCreativeTab(CreativeTabs.COMBAT);
        }
    
        /*
            * Définit le nom dans le registre
            * Définit l'unlocalized name
            * @Param item L'item
            * @Param itemName le nom de l'item
            */
        static void setItemName(final Item item, final String itemName) {
    
            item.setRegistryName(TestMod.MOD_ID, itemName);
            item.setUnlocalizedName(item.getRegistryName().toString());
        }
    }
    
    

    La méthode setCreativeTab permet d'affilier l'item à un onglet en mode créatif.
    La méthode setRegistryName permet d'enregistrer l'item dans le registre.
    La méthode setUnlocalizedName permet de définir le nom non localisé de l'item, "item." sera automatiquement ajouté devant celui-ci.

    :interrogation: La fonction setItemName, initiant les noms des items, sera utilisée par tous les items du mod, c'est pourquoi elle est déclarée public static et prend l'item en paramètre. En effet, certains items (par exemple des épées ou des armures) doivent étendre les classes de Minecraft correspondantes plutôt que notre classe parente.
    :interrogation: Cette classe sera aussi la classe utilisée par les items basiques, n'ayant pas d'effets. Par exemple, un item intermédiaire dans un craft.

    •                                      *
      

    Item complexe et NBTTags ★☆☆☆☆

    Avant de créer un Item complexe, voyons quelques exemples de fonctions que nous pourrions utiliser :

    • Item#onItemUse -> Quand l'on fait un clic droit sur un block
    • Item#onItemRightClick -> Quand l'on fait un clic droit
    • Item#onBlockDestroyed -> Quand on détruit un block
    • Item#onUpdate -> Appelée chaque tick tant que l'item est dans l'inventaire
    • Item#onLeftClickEntity -> Quand on attaque une entité avec l'item

    :interrogation: Ce n'est que quelques exemples, si vous avez besoin d'une fonction en particulier, vous pouvez chercher dans net.minecraft.item.Item.

    Les **NBTTagCompound **(ou NBTTags), sont des objets très simples d'utilisation permettant à un itemstack (une instance d'un item en jeu) de sauvegarder des informations à tout moment, et de les réutiliser plus tard. Voici comment les utiliser (ici l'ItemStack s'appelle stack) :

    Vérifier si les NBTTags ont été initialisés :

    stack.hasTagCompound()
    

    Initialiser les NBTTags :

    stack.setTagCompound(new NBTTagCompound());
    

    Récupérer les NBTTags :

    stack.getTagCompound()
    

    Enregistrer une valeur (key étant un String servant de clé, et value étant la valeur du type que l'on utilise) :

    stack.getTagCompound().setInteger(key, value); // ou setDouble, setString, etc
    

    Récupérer une valeur :

    stack.getTagCompound().getInteger(key); // Ou getDouble, getString, etc
    

    Pour l'exemple, nous allons faire un item qui, en faisant shift + clic droit, enregistre une position, et qui permet de s'y téléporter en faisant un simple clic droit.
    Pour cela on va utiliser la fonction onItemRightClick (vue ci-dessus), et nous aurons également besoin des NBTTags.

    
    package net.dylem.test_mod.item;
    
    import net.minecraft.entity.player.EntityPlayer;
    import net.minecraft.item.ItemStack;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.util.ActionResult;
    import net.minecraft.util.EnumActionResult;
    import net.minecraft.util.EnumHand;
    import net.minecraft.util.math.BlockPos;
    import net.minecraft.world.World;
    
    /*
        * Un totem qui permet au joueur de se téléporter à une position enregistrée au préalable
        */
    public class ItemTeleportationTotem extends ItemTestMod {
    
        public ItemTeleportationTotem(String itemName) {
    
            super(itemName);
        }
    
        /* Permet d'effectuer une action en appuyant sur le clic droit avec l'item en main
            * 
            * @see net.minecraft.item.Item#onItemRightClick(net.minecraft.world.World, net.minecraft.entity.player.EntityPlayer, net.minecraft.util.EnumHand)
            * @Param world Le monde
            * @Param player Le joueur
            * @Param hand La main dans laquelle l'item est tenu
            * @Return Le résultat de l'action, si tout s'est bien déroulé
            */
        @Override
        public ActionResult<ItemStack> onItemRightClick(World world, EntityPlayer player, EnumHand hand) {
    
            final ItemStack stack = player.getHeldItem(hand);
    
            if(player.isSneaking()) { // Si le joueur est en train d'appuyer sur shift
    
                if(!stack.hasTagCompound()) { // Initialisation du NBTTag
                    stack.setTagCompound(new NBTTagCompound());
                }
    
                // On enregistre la position actuelle
                BlockPos pos = player.getPosition();
                int[] posArray = { pos.getX(), pos.getY(), pos.getZ() };
                stack.getTagCompound().setIntArray("pos", posArray);
    
            } else {
    
                BlockPos pos;
    
                if(stack.hasTagCompound()) { // Si on a enregistré une position
                    // Récupération de la position
                    int[] posArray = stack.getTagCompound().getIntArray("pos");
                    pos = new BlockPos(posArray[0], posArray[1], posArray[2]);
                } else { // sinon, on se téléporte au spawn
                    pos = world.getSpawnPoint();
                }
    
                while(!world.isAirBlock(pos)) { // Au cas où le point de téléportation ne contiendrait pas de l'air
                    pos = pos.up(); // Alors on le met plus haut
                    System.out.println(pos.getY());
                }
                player.setPosition(pos.getX(), pos.getY(), pos.getZ()); // Téléportation
            }
    
            return new ActionResult<ItemStack>(EnumActionResult.SUCCESS, stack);
        }
    }
    
    

    :interrogation: Si vous avez besoin d'aide pour l'utilisation de telle ou telle fonction, vous pouvez demander.

    Metadata et Item avec variantes ★★★☆☆

    Bon, je vous préviens, là ça va commencer à piquer 🙂

    La metadata correspond à la propriété des items avec plusieurs variantes (exemple : les colorants), et permet de regrouper plusieurs milliers d'items similaires sous la même ID.

    Pour créer un Item avec des variantes, nous aurons besoin de trois fichiers :

    • ItemVariants -> La classe de notre Item
    • EnumItemVariants -> L'énumération qui contient les variantes de notre Item
    • IVariant -> Des interfaces d'aide à la création d'Items à variantes

    IVariant

    Commençons logiquement par les interfaces qu'utiliseront notre énumération et notre classe principale :

    
    package net.dylem.test_mod.util;
    
    import net.minecraft.util.IStringSerializable;
    
    public interface IVariant {
    
        /*
            * Classe d'aide à la création d'Enum pour les items à multiples variantes
            */
        public interface IEnumVariant extends IStringSerializable {
    
            /*
                * Permet de récupérer la metadata d'une variante
                * @Return la metadata
                */
            int getMeta();
        }
    
        /*
            * Classe d'aide à la création d'items à multiples variantes
            */
        public interface IItemVariant {
    
            /*
                * Permet de récuperer les valeurs des variantes
                * @Return le tableau comprenant les valeurs des variantes
                */
            <T extends IEnumVariant> T[] getValues();
        }
    }
    
    

    Ces interfaces seront utilisées par l'enregistrement du modèle des items à variantes (prochain chapitre) :

    • IEnumVariant permet de récupérer la metadata d'une variante, en ayant seulement comme information que l'énumération étend cette interface. Par ailleurs, l'énumération doit étendre IStringSerializable, c'est pourquoi notre interface le fait à sa place.
    • IItemVariant permet de récupérer les variantes d'un item, en ayant seulement comme information que celui-ci étend cette interface. L'interface permet aussi de savoir si un item contient des variantes ou non (la méthode setHasSubtypes() pouvant avoir des résultats aléatoires avec les outils/épées avec variantes).

    EnumItemVariants

    C'est parti pour l'énumération, qui aura classiquement comme nom EnumNomDeL'Item.
    Celle-ci contient les variantes, et permet de récupérer leur metadata, et leur nom, ainsi que de récupérer une variante en fonction de la metadata.

    
    package net.dylem.test_mod.item;
    
    import net.dylem.test_mod.util.IVariant;
    
    /*
        * Lié à ItemVariants, contenant ses variantes
        */
    public enum EnumItemVariants implements IVariant.IEnumVariant {
    
        // Le nom correspond à la deuxième partie de l'unlocalized name de la variante
        A(0, "a"),
        B(1, "b"),
        C(2, "c");
    
        private static final EnumItemVariants[] META_LOOKUP = new EnumItemVariants[values().length];
        private final int meta;
        private final String name;
    
        /*
            * Crée une nouvelle variante
            * @Param metaIn la meta de la variante
            * @Param nameIn le nom
            * @Param chatColorIn la couleur du texte
            */
        private EnumItemVariants(int metaIn, String nameIn) {
    
            this.meta = metaIn;
            this.name = nameIn;
        } 
    
        /*
            * Retourne la meta de la variante
            * @return meta
            */
        @Override
        public int getMeta() {
    
            return meta;
        }
    
        /*
            * Retourne le nom de la variante
            * @return name
            */
        @Override
        public String getName() {
    
            return name;
        }
    
        /* 
            * Retourne la variante (l'unlocalized name) correspondant à la meta
            * @Param la meta à chercher
            * @return A, B ou C en fonction de la meta
            */
        public static EnumItemVariants byMetadata(int meta)
        {
            if (meta < 0 || meta >= META_LOOKUP.length) {
                meta = 0;
            }
    
            return META_LOOKUP[meta];
        }
    
            static {
                for (EnumItemVariants enumType : values()) {
                    META_LOOKUP[enumType.getMeta()] = enumType;
                }
            }
    }
    
    

    J'ai ici appelé les variantes A, B et C parce que je vais créer des lettres (qui ne font rien), mais je vous encourage à nommer vos variantes en fonction de leur utilité/spécificité.
    META_LOOKUP contient toutes les variantes en fonction de leur metadata, meta et name sont des propriétés propres à chaque variante.

    :interrogation: L'énumération est fortement inspirée de net.minecraft.item.EnumDyeColor, on enlève juste ce qui ne nous intéresse pas.

    ItemVariants

    Et enfin l'Item :

    
    package net.dylem.test_mod.item;
    
    import java.util.List;
    import java.util.stream.Collectors;
    import java.util.stream.Stream;
    
    import net.dylem.test_mod.util.IVariant;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.item.ItemStack;
    import net.minecraft.util.NonNullList;
    import net.minecraftforge.fml.relauncher.Side;
    import net.minecraftforge.fml.relauncher.SideOnly;
    
    /*
        * item avec plusieurs variantes
        */
    public class ItemVariants extends ItemTestMod implements IVariant.IItemVariant {
    
        public ItemVariants(final String itemName) {
    
            super(itemName);
        }
    
        /*
            * Retourne l'unlocalized name correspondant à la variante
            * @Param stack l'ItemStack
            * @Return la variante correspondant à stack
            * @see net.minecraft.item.Item#getUnlocalizedName(net.minecraft.item.ItemStack)
            */
        @Override
            public String getUnlocalizedName(ItemStack stack) {
    
            int i = stack.getMetadata();
            return super.getUnlocalizedName() + "." + EnumItemVariants.byMetadata(i).getName();
        }
    
        /*
            * Permet de récupérer les items des variantes
            * @Param tab (CreativeTabs)
            * @Param subItems les variantes
            * @see net.minecraft.item.Item#getSubItems(net.minecraft.creativetab.CreativeTabs, net.minecraft.util.NonNullList)
            */
        @SideOnly(Side.CLIENT)
        @Override
        public void getSubItems(final CreativeTabs tab, final NonNullList<ItemStack> subItems) {
    
            final List<ItemStack> items = Stream.of(EnumItemVariants.values())
                    .map(enumType -> new ItemStack(this, 1, enumType.getMeta())) // Pour chaque variante, on crée un itemstack en fonction de sa meta
                    .collect(Collectors.toList()); // Et on les mets dans une liste
    
            subItems.addAll(items);
        }
    
        /*
            * @see net.dylem.test_mod.util.IVariant.IItemVariant#getValues()
            */
        @Override
        public EnumItemVariants[] getValues() {
    
            return EnumItemVariants.values();
        }
    }
    
    
    • La fonction getValues() provient simplement de notre interface, nous l'utiliseront au prochain chapitre pour enregistrer les modèles.
    • La fonction getUnlocalizedName() est réécrite pour permettre à chaque variante d'avoir son nom non localisé (on rajoute le nom de la variante après un point)
    • La fonction getSubItems() permet de récupérer toutes les variantes sous forme d'itemstacks

    :interrogation:Pour les outils et les épées, vous devrez les rendre inépuisables en rajoutant ceci dans votre constructeur, afin que Minecraft ne confonde pas durabilité et metadata :

    this.setMaxDamage(-1);
    

    Enregistrement des Items et de leur modèle ★★★★☆

    On y arrive enfin, l'enregistrement des items et de leur modèle :

    
    package net.dylem.test_mod.init;
    
    import java.util.HashSet;
    
    import net.dylem.test_mod.item.ItemSoulSword;
    import net.dylem.test_mod.item.ItemTeleportationTotem;
    import net.dylem.test_mod.item.ItemTestMod;
    import net.dylem.test_mod.item.ItemVariants;
    import net.dylem.test_mod.util.IVariant;
    import net.dylem.test_mod.util.client.renderer.MeshDefinitionFix;
    import net.minecraft.client.renderer.ItemMeshDefinition;
    import net.minecraft.client.renderer.block.model.ModelBakery;
    import net.minecraft.client.renderer.block.model.ModelResourceLocation;
    import net.minecraft.item.Item;
    import net.minecraftforge.client.event.ModelRegistryEvent;
    import net.minecraftforge.client.model.ModelLoader;
    import net.minecraftforge.event.RegistryEvent;
    import net.minecraftforge.fml.common.Mod;
    import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
    import net.minecraftforge.fml.relauncher.Side;
    import net.minecraftforge.registries.IForgeRegistry;
    
    /* 
        * Déclare et instancie les items du mod
        */
    public class ModItems {
    
        // Item de base ne faisant rien, avec un joli Kappa comme texture
        public static final ItemTestMod ITEM_BASIC = new ItemTestMod("item_basic");
    
        // Un totem qui permet de se téléporter à une position enregistrée
        public static final ItemTeleportationTotem TELEPORTATION_TOTEM = new ItemTeleportationTotem("teleportation_totem");
    
        // Item à trois variantes, les lettres A, B et C
        public static final ItemVariants ITEM_VARIANTS = new ItemVariants("item_variants");
    
        // Une épée qui garde les ames des énemis en mémoire, la rendant plus puissante
        public static final ItemSoulSword SOUL_SWORD = new ItemSoulSword(Item.ToolMaterial.DIAMOND, "soul_sword");
    
        /*
            * Contient tous les items enregistrés
            * HashSet -> pas de doublons
            */
        public static final HashSet REGISTERED_ITEMS = new HashSet();
    
        /* 
            * Enregistre les items
            * Classe automatiquement abonnée à MinecraftForge.EVENT_BUS
            */
        @Mod.EventBusSubscriber
        public static class RegistrationHandler {
    
            /* 
                * Enregistre les items
                * @param event L'évènement
                */
            @SubscribeEvent
            public static void registerItems(final RegistryEvent.Register event) {
    
                // Contient tous les items du mod, pas encore enregistrés
                final Item[] items = {
                    ITEM_BASIC,    
                    TELEPORTATION_TOTEM,
                    ITEM_VARIANTS,
                    SOUL_SWORD,
                };
    
                final IForgeRegistry registry = event.getRegistry();
    
                /*
                    * Pour chaque item, on l'enregistre à l'aide de l'évènement
                    * Puis on l'ajoute au set d'item enregistrés, pour plus tard enregistrer son modèle
                    */
                for(final Item item : items) {
                    registry.register(item);
                    REGISTERED_ITEMS.add(item);
                }
            }
        }
    
        /*
            * Enregistre les modèles des items
            * Classe automatiquement abonnée à MinecraftForge.EVENT_BUS côté CLIENT
            */
        @Mod.EventBusSubscriber(Side.CLIENT)
        public static class ModelHandler {
    
            private static final ModelHandler INSTANCE = new ModelHandler();
    
            /*
                * Enregistre les modèles des items
                * @Param event L'évènement
                */
            @SubscribeEvent
            public static void registerItemModels(final ModelRegistryEvent event) {
    
                REGISTERED_ITEMS.forEach(item -> { // Pour chaque Item enregistré, ..
    
                    final String modelLocation = item.getRegistryName().toString();
    
                    // S'il possède des variantes
                    if(item instanceof IVariant.IItemVariant) {
    
                        IVariant.IEnumVariant values[] = ((IVariant.IItemVariant)item).getValues();
    
                        if(values != null) {
                            INSTANCE.registerVariantItemModels(item, modelLocation, values);
                        }
                    }
                    else {
    
                        final ModelResourceLocation fullModelLocation = new ModelResourceLocation(modelLocation, "inventory");
                        INSTANCE.registerItemModel(item, fullModelLocation);
                    }
                });
            }
    
            /*
                * Enregistre le modèle d'un item
                * Etape 2 : Permet de récupérer la meshDefinition de l'item
                * @Param item L'item
                * @Param modelLocation la localisation complète de l'item
                */
            private void registerItemModel(final Item item, final ModelResourceLocation fullModelLocation) {
    
                ModelBakery.registerItemVariants(item, fullModelLocation);
                registerItemModel(item, MeshDefinitionFix.create(stack -> fullModelLocation));
            }
    
            /*
                * Enregistre le modèle d'un item
                * @Param item L'item
                * @Param meshDefinition la meshDefinition de l'item
                */
            private void registerItemModel(final Item item, final ItemMeshDefinition meshDefinition) {
    
                ModelLoader.setCustomMeshDefinition(item, meshDefinition);
            }
    
            /*
                * Enregistre les modèles d'un item avec variantes
                * @Param item L'item
                * @Param modelLocation le nom de l'item dans le registre
                * @Param values les variantes de l'item
                */
            private <T extends IVariant.IEnumVariant> void registerVariantItemModels(final Item item, final String modelLocation, final T[] values) {
    
                for (final T value : values) {
                    registerItemModelForMeta(item, value.getMeta(), modelLocation + "=" + value.getName());
                }
            }
    
            /*
                * Rengistre la variante d'un item
                * @Param item L'item
                * @Param metadata la meta de la variante
                * @Param variantLocation la localisation de la variante
                */
            private void registerItemModelForMeta(final Item item, final int metadata, final String variantLocation) {
    
                ModelResourceLocation location = new ModelResourceLocation(item.getRegistryName(), variantLocation);
                ModelLoader.setCustomModelResourceLocation(item, metadata, location);
            }
        }
    }
    
    

    Comme vous pouvez le voir, nous déclarons des instances de nos items (spoil : soul sword correspond au bonus du tutoriel). Un évènement permet ensuite de les enregistrer, et les ajoute à une liste d'items enregistrés. Puis un autre évènement récupère cette liste et enregistre leurs modèles.

    :interrogation: Les annotations @Mod.EventBusSubscriber avant les classes sont importantes, elles permettent d'ajoute automatiquement la classe au bus d'évènements de forge (côté client pour les modèles).

    Quand forge enregistre les items, il envoie un évènement de type [font=monospaceRegistryEvent][font=monospace.][font=monospaceRegister][font=monospace<][font=monospaceItem][font=monospace>], que nous capturons, puis nous utilisons la fonction [font=monospaceIForgeRegistry.register()] pour enregistrer nos items.

    Pour les modèles, ça se corse un peu, alors nous allons voir ça plus en détail.

    L'enregistrement des modèles plus en détail

    Commencez par ajouter cette interface à votre projet :

    
    package net.dylem.test_mod.util.client.renderer;
    
    import net.minecraft.client.renderer.ItemMeshDefinition;
    import net.minecraft.client.renderer.block.model.ModelResourceLocation;
    import net.minecraft.item.ItemStack;
    
    /**
    * Permet l'utilisation des lambdas sans causer d'erreur à ForgeGradle
    * @author diesieben07
    */
    public interface MeshDefinitionFix extends ItemMeshDefinition {
        ModelResourceLocation getLocation(final ItemStack stack);
    
        /*
        * Méthode d'aide pour créer facilement les instances lambda de cette classe
        * @Param lambda Le lambda de ce type
        * @Return l'instance de type ItemMeshDefinition correspondant
        */
        static ItemMeshDefinition create(final MeshDefinitionFix lambda) {
            return lambda;
        }
    
        /*
        * @see net.minecraft.client.renderer.ItemMeshDefinition#getModelLocation(net.minecraft.item.ItemStack)
        */
        @Override
        default ModelResourceLocation getModelLocation(final ItemStack stack) {
            return getLocation(stack);
        }
    }
    
    

    :interrogation: Vous n'avez pas besoin de la comprendre, sachez juste qu'elle va nous aider à utiliser les fonctions lambda de Java 8 sans causer de problème à forge, et qu'elle a été créée par diesieben07

    Voyons le code de la classe point par point :

    Premièrement, on crée une instance de notre classe pour pouvoir déclarer privées les fonctions d'enregistrement de modèles (parce que l'encapsulation c'est important) :

    private static final ModelHandler INSTANCE = new ModelHandler();
    

    Puis, on crée la méthode qui capture l'évènement :

    
    /*
        * Enregistre les modèles des items
        * @Param event L'évènement
        */
    @SubscribeEvent
    public static void registerItemModels(final ModelRegistryEvent event) {
    
        REGISTERED_ITEMS.forEach(item -> { // Pour chaque Item enregistré, ..
    
            final String modelLocation = item.getRegistryName().toString();
    
            // S'il possède des variantes
            if(item instanceof IVariant.IItemVariant) {
    
                IVariant.IEnumVariant values[] = ((IVariant.IItemVariant)item).getValues();
    
                if(values != null) {
                    INSTANCE.registerVariantItemModels(item, modelLocation, values);
                }
            }
            else {
    
                final ModelResourceLocation fullModelLocation = new ModelResourceLocation(modelLocation, "inventory");
                INSTANCE.registerItemModel(item, fullModelLocation);
            }
        });
    }
    
    

    La fonction contient principalement une redirection des items à variantes vers registerVariantItemModels, et des autres items vers registerItemModel.

    :interrogation: C'est là que l'interface IItemVariant entre en jeu. On vérifie si l'item implémente cette interface, et si c'est le cas, on appèle la fonction de l'interface getValues() pour récupérer les valeurs des variantes.

    Pour les items sans variantes :

    
    /*
        * Enregistre le modèle d'un item
        * Etape 2 : Permet de récupérer la meshDefinition de l'item
        * @Param item L'item
        * @Param modelLocation la localisation complète de l'item
        */
    private void registerItemModel(final Item item, final ModelResourceLocation fullModelLocation) {
    
        ModelBakery.registerItemVariants(item, fullModelLocation);
        registerItemModel(item, MeshDefinitionFix.create(stack -> fullModelLocation));
    }
    
    /*
        * Enregistre le modèle d'un item
        * @Param item L'item
        * @Param meshDefinition la meshDefinition de l'item
        */
    private void registerItemModel(final Item item, final ItemMeshDefinition meshDefinition) {
    
        ModelLoader.setCustomMeshDefinition(item, meshDefinition);
    }
    
    
    • ModelBakery.registerItemVariants permet de s'assurer que les modèles par défaut ne soient pas utilisés (Appelé automatiquement par les items à variantes, donc on ne le met qu'ici).
    • ModelLoader.setCustomMeshDefinition permet d'enregistrer le modèle de l'item sans variantes.

    Pour les items avec variantes :

    /*
    * Enregistre les modèles d'un item avec variantes
    * @Param item L'item
    * @Param modelLocation le nom de l'item dans le registre
    * @Param values les variantes de l'item
    */
    private <T extends IVariant.IEnumVariant> void registerVariantItemModels(final Item item, final String modelLocation, final T[] values) {
    
        for (final T value : values) {
            registerItemModelForMeta(item, value.getMeta(), modelLocation + "=" + value.getName());
        }
    }
    
    /*
    * Rengistre la variante d'un item
    * @Param item L'item
    * @Param metadata la meta de la variante
    * @Param variantLocation la localisation de la variante
    */
    private void registerItemModelForMeta(final Item item, final int metadata, final String variantLocation) {
    
        ModelResourceLocation location = new ModelResourceLocation(item.getRegistryName(), variantLocation);
        ModelLoader.setCustomModelResourceLocation(item, metadata, location);
    }
    
    • Dans registerVariantItemModels, on utilise les propriétés de notre énumération pour récupérer la metadata et le nom de chaque variante.
    • Dans registerItemModelForMeta, on crée une ModelResourceLocation (une localisation du modèle) personnalisée qu'on envoie à ModelLoader.setCustomModelResourceLocation.

    Localisation des ressources

    Pour les items sans variantes, les modèles doivent se trouver dans assets/modid/models/item/nom_de_l'item.json.
    Pour un item classique, le contenu du fichier ressemblera à ça :

    {
        "parent": "item/generated",
        "textures": {
        "layer0": "test_mod:items/item_basic"
        }
    }
    

    Remplacez bien entendu test_mod par votre modid et item_basic par le nom de votre item.

    Pour les items avec variantes, c'est un peu plus compliqué. Vous n'avez pas besoin de modèle, mais d'un blockstate qui se trouvera dans assets/modid/blockstates/nom_de_l'item.json

    {
        "forge_marker": 1,
        "defaults": {
            "model": "test_mod:item/generated"
        },
        "variants": {
            "test_mod:item_variants": {
                "a": {
                    "textures": {
                        "layer0": "test_mod:items/variants.a"
                    }
                },
                "b": {
                    "textures": {
                        "layer0": "test_mod:items/variants.b"
                    }
                },
                "c": {
                    "textures": {
                        "layer0": "test_mod:items/variants.c"
                    }
                }
            }
        }
    }
    

    Remplacez a, b et c par le nom de vos variantes.

    Vous aurez aussi besoin d'un modèle générique pour vos items à variantes situé dans assets/modid/models/block/item/generated.json

    {
        "parent": "minecraft:item/generated"
    }
    

    :interrogation: Vous pouvez aussi prendre pour parent handheld pour les épées par exemple.

    Enfin, vos textures doivent se trouver dans assets/modid/textures/items, au format PNG.

    Bonus : Un exercice pas si facile ★★★★★

    Voilà les règles de l'exercice :

    Vous devez créer une épée avec de la metadata (pour rappel, vous devez retirer sa durabilité en la mettant à -1). À chaque fois qu'un ennemi sera tué par cette épée, son attaque et sa vitesse d'attaque augmenteront de 0.1. À certains paliers (10 ; 25 ; 50), son attaque et sa vitesse d'attaques ajoutées (sans prendre en compte celles de base) se verront multipliées par 2. De plus, lorsque ces paliers seront atteins, l'épée changera de metadata (sa metadata augmentera de 1). Les variantes de l'épée sont :

    • COMMON
    • RARE
    • EPIC
    • LEGENDARY
      :interrogation: Je me doute bien que personne ne vas tenter l'exercice car il est bien trop compliqué, cependant si vous ne voulez-pas le faire, regardez au moins la correction car il met en scène tous les points du tutoriel.

    Correction

    ItemSoulSword

    
        package net.dylem.test_mod.item;
    
    import java.util.Collection;
    import java.util.List;
    import java.util.Optional;
    import java.util.UUID;
    import java.util.stream.Collectors;
    import java.util.stream.IntStream;
    import java.util.stream.Stream;
    
    import com.google.common.collect.Multimap;
    
    import net.dylem.test_mod.TestMod;
    import net.dylem.test_mod.util.IVariant;
    import net.minecraft.creativetab.CreativeTabs;
    import net.minecraft.entity.SharedMonsterAttributes;
    import net.minecraft.entity.ai.attributes.AttributeModifier;
    import net.minecraft.entity.ai.attributes.IAttribute;
    import net.minecraft.inventory.EntityEquipmentSlot;
    import net.minecraft.item.Item;
    import net.minecraft.item.ItemStack;
    import net.minecraft.item.ItemSword;
    import net.minecraft.nbt.NBTTagCompound;
    import net.minecraft.util.NonNullList;
    import net.minecraftforge.fml.relauncher.Side;
    import net.minecraftforge.fml.relauncher.SideOnly;
    
    /*
        * Une épée qui garde les ames des énemis en mémoire,
        * dont la puissance (attaque/vitesse) augmente de 0.1 pour chaque ennemi tué,
        * et qui double de puissance à certains paliers
        */
    public class ItemSoulSword extends ItemSword implements IVariant.IItemVariant {
    
        // Etapes de changement de metadata
        private final int META_STEPS[] = {10, 25, 50};
    
        public ItemSoulSword(Item.ToolMaterial material, final String itemName) {
    
            super(material);
    
            ItemTestMod.setItemName(this, itemName);
            setCreativeTab(TestMod.TEST_TAB);
    
            // Les damages de l'Item sont déjà utilisés par la metadata
            this.setMaxDamage(-1);
        }
    
        /*
            * Initialise le nombre d'ames de ce stack
            * @Param stack L'itemstack
            */
        private void initiateSouls(ItemStack stack) {
    
            stack.setTagCompound(new NBTTagCompound());
            stack.getTagCompound().setInteger("souls", 0); // nombre d'ames
            stack.getTagCompound().setDouble("increase", 0); // augmentation
        }
    
        /*
            * Ajouter 1 au nombre d'ames et ajoute 0.1 de puissance
            * Si le nombre d'ames atteint un palier, change la métadata et multiplie par 2 la puissance ajoutée
            * 
            * @Param stack L'itemstack
            * @Return stack L'itemstack
            */
        public ItemStack addSoul(ItemStack stack) {
    
            final int i = getSouls(stack) + 1;
            final double d = getIncrease(stack) + 0.1;
    
            // Envoi du nouveau nombre d'ames
            stack.getTagCompound().setInteger("souls", i);
    
            if(IntStream.of(META_STEPS).anyMatch(step -> i == step)) { // Si on atteint une étape, 
    
                stack.getTagCompound().setDouble("increase", d * 2); // double la puissance
                stack.setItemDamage(stack.getItemDamage() + 1); // augmente la metadata
            }
            else {
                stack.getTagCompound().setDouble("increase", d); // sinon on envoie l'augmentation normale
            }
    
            return stack;
        }
    
        /*
            * @Param stack L'itemStack
            * @Return le nombre d'ames "capturées"
            */
        private int getSouls(ItemStack stack) {
    
            if (!stack.hasTagCompound()) {
                initiateSouls(stack);
            }
    
            return stack.getTagCompound().getInteger("souls");
        }
    
        /*
            * @Param stack L'itemStack
            * @Return l'augmentation actuelle
            */
        private double getIncrease(ItemStack stack) {
    
            return stack.getTagCompound().getDouble("increase");
        }
    
        /*
            * Permet de récupérer les attributs modifiés de l'arme
            * 
            * @see net.minecraft.item.Item#getAttributeModifiers(net.minecraft.inventory.EntityEquipmentSlot, net.minecraft.item.ItemStack)
            * @Param slot Le slot actuellement utilisé
            * @Param stack L'itemstack dans le slot
            * @return modifier La map modifiée
            */
        @Override
        public Multimap<String, AttributeModifier> getAttributeModifiers(final EntityEquipmentSlot slot, final ItemStack stack) {
    
            // Si on a tué au moins une entité, et que l'item est en utilisation, alors on augmente la puissance de l'arme
            if (getSouls(stack) > 0 && slot == EntityEquipmentSlot.MAINHAND) {
    
                Multimap <String, AttributeModifier> modifiers = super.getAttributeModifiers(EntityEquipmentSlot.MAINHAND, stack);
                alterAttributeModifier(modifiers, SharedMonsterAttributes.ATTACK_DAMAGE, ATTACK_DAMAGE_MODIFIER, getIncrease(stack));
                alterAttributeModifier(modifiers, SharedMonsterAttributes.ATTACK_SPEED, ATTACK_SPEED_MODIFIER, getIncrease(stack));
    
                return modifiers;
            }
    
            // Sinon on utilise la fonction d'ItemSword
            return getItemAttributeModifiers(slot);
        }
    
        /*
            * Altère un modifieur d'attribut
            * 
            * @Param multimap La multimap
            * @Param modifierName le nom de l'attribut modifié
            * @Param id l'id du modifieur 
            * @Param d le multiplicateur
            */
        private void alterAttributeModifier(final Multimap <String, AttributeModifier> multimap, 
                        final IAttribute attribute, final UUID id, final double d) {
    
            final Collection<AttributeModifier> modifiers = multimap.get(attribute.getName());
            final Optional<AttributeModifier> optional = modifiers.stream().filter(attributeModifier -> attributeModifier.getID().equals(id)).findFirst();
    
            optional.ifPresent(modifier -> {
    
                AttributeModifier newModifier = new AttributeModifier(modifier.getID(), modifier.getName(), modifier.getAmount() + d, modifier.getOperation());
    
                modifiers.remove(modifier);
                modifiers.add(newModifier);
            });
        }
    
        /*
            * Retourne l'unlocalized name correspondant à la variante
            * @Param stack l'ItemStack
            * @Return la variante correspondant à stack
            * @see net.minecraft.item.Item#getUnlocalizedName(net.minecraft.item.ItemStack)
            */
        @Override
            public String getUnlocalizedName(ItemStack stack) {
    
            int i = stack.getMetadata();
            return super.getUnlocalizedName() + "." + EnumSoulSword.byMetadata(i).getName();
        }
    
        /*
            * Permet de récupérer les items des variantes
            * @Param tab (CreativeTabs)
            * @Param subItems les variantes
            * @see net.minecraft.item.Item#getSubItems(net.minecraft.creativetab.CreativeTabs, net.minecraft.util.NonNullList)
            */
        @SideOnly(Side.CLIENT)
        @Override
        public void getSubItems(final CreativeTabs tab, final NonNullList<ItemStack> subItems) {
    
            final List <ItemStack> items = Stream.of(EnumSoulSword.values())
                    .map(enumType -> new ItemStack(this, 1, enumType.getMeta()))
                    .collect(Collectors.toList());
    
            subItems.addAll(items);
        }
    
        /*
            * @see net.dylem.test_mod.util.IVariant.IItemVariant#getValues()
            */
        @Override
        public EnumSoulSword[] getValues() {
    
            return EnumSoulSword.values();
        }
    }
    
    

    EnumSoulSword

    
    package net.dylem.test_mod.item;
    
    import net.dylem.test_mod.util.IVariant;
    import net.minecraft.util.text.TextFormatting;
    
    public enum EnumSoulSword implements IVariant.IEnumVariant {
    
        // Le nom correspond à la deuxième partie de l'unlocalized name de la variante
        COMMON(0, "common"),
        RARE(1, "rare"),
        EPIC(2, "epic"),
        LEGENDARY(3, "legendary");
    
        private static final EnumSoulSword[] META_LOOKUP = new EnumSoulSword[values().length];
        private final int meta;
        private final String name;
    
        /*
            * Crée une nouvelle variante
            * @Param metaIn la meta de la variante
            * @Param nameIn le nom
            * @Param chatColorIn la couleur du texte
            */
        private EnumSoulSword(int metaIn, String nameIn) {
    
            this.meta = metaIn;
            this.name = nameIn;
        } 
    
        /*
            * Retourne la meta de la variante
            * @return meta
            */
        @Override
        public int getMeta() {
    
            return meta;
        }
    
        /*
            * Retourne le nom de la variante
            * @return name
            */
        @Override
        public String getName() {
    
            return name;
        }
    
        /* 
            * Retourne la variante (l'unlocalized name) correspondant à la meta
            * @Param la meta à chercher
            * @return A, B ou C en fonction de la meta
            */
        public static EnumSoulSword byMetadata(int meta)
        {
            if (meta < 0 || meta >= META_LOOKUP.length) {
                meta = 0;
            }
    
            return META_LOOKUP[meta];
        }
    
            static {
                for (EnumSoulSword enumType : values()) {
                    META_LOOKUP[enumType.getMeta()] = enumType;
                }
            }
    
    }
    
    

    ModEntityEvent

    
    package net.dylem.test_mod.event;
    
    import java.util.Optional;
    
    import net.dylem.test_mod.item.EnumSoulSword;
    import net.dylem.test_mod.item.ItemSoulSword;
    import net.minecraft.entity.EntityLivingBase;
    import net.minecraft.inventory.EntityEquipmentSlot;
    import net.minecraft.item.ItemStack;
    import net.minecraft.util.EntityDamageSource;
    import net.minecraft.util.EnumHand;
    import net.minecraftforge.event.entity.living.LivingDeathEvent;
    import net.minecraftforge.fml.common.Mod;
    import net.minecraftforge.fml.common.eventhandler.SubscribeEvent;
    
    /*
        * Classe contenant tous les évènements du mod involvant une entité.
        * Classe automatiquement abonnée à MinecraftForge.EVENT_BUS
        */
    @Mod.EventBusSubscriber
    public class ModEntityEvent {
    
        /*
            * Si une entité a été tuée par un certain item.
            * 
            * Evènement éxécuté par :
            * {@link EntityLivingBase#onDeath(DamageSource)},
            * {@link EntityPlayer#onDeath(DamageSource)},
            * {@link EntityPlayerMP#onDeath(DamageSource)}.
            * 
            * @Param event L'évènement
            */
        @SubscribeEvent
        public static void entityKilledByItem(LivingDeathEvent event) {
    
            // Si l'évènement a été causé par une entité
            if(event.getSource() instanceof EntityDamageSource) {
    
                // Forcément une EntityLivingBase (voir @link)
                EntityLivingBase entity = (EntityLivingBase) ((EntityDamageSource)event.getSource()).getTrueSource();
    
                ItemStack stack = entity.getHeldItemMainhand();
    
                // @see net.dylem.test_mod.item.ItemBloodSword
                if(stack != null && stack.getItem() instanceof ItemSoulSword) {
    
                    entity.setHeldItem(EnumHand.MAIN_HAND, ((ItemSoulSword)stack.getItem()).addSoul(stack));
                }    
            }
        }
    }
    

    Résultat

    Je vous laisserai tester tous les items, vous pouvez télécharger le projet sur github à cette adresse : https://github.com/dylem/DylemTestMod

    Quelques images tout de même :

    Crédits

    Rédaction :

    • Dylem

    Correction :


    Ce tutoriel de Dylem 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



  • Très beau tutoriel ! Complet et propre tu as du mettre un bon bout de temps a le réaliser !



  • @'aypristyle':

    Très beau tutoriel ! Complet et propre tu as du mettre un bon bout de temps a le réaliser !

    Merci 🙂

    Hésite pas à demander si tu as besoin de précisions pour tel ou tel point !