Utiliser les événements


  • Administrateurs

    youtubeCe tutoriel est également disponible en vidéo.

    Sommaire

    Introduction

    Lors du développement de la version 2.0 de buildcraft, SpaceToad a voulu ajouter du pétrole. Le problème, c'est que pour ajouter un fluide il faut modifier la classe du seau de minecraft, ce qui aurait causé une incompatibilité avec les autres mods voulant ajouter un fluide. Pour corriger ce problème, SpaceToad a eu l'idée de créer une api qui modifie les classes de minecraft, et permettrait aux mods utilisant cette api d'ajouter des fluides sans modifier les classes de minecraft. Et c'est ainsi que forge à été créé, en effet la fonctionnalité pour les seaux était la première de forge ( le 4ème commit sur github)
    Depuis, avec l'ASM un système d’événement a été mit en place, plus optimisé. Tout ça pour dire que les événements font partis des fonctionnalités les plus importe de forge, puis qu'ils permettent de faire des choses qui serait normalement impossible sans modifier les classes de minecraft.

    En revanche, les événements sont très varier, vous comprendrez qu'il n'est pas possible de tous les présenter. D'ailleurs, les possibilités des événements sont aussi très varier, on peut faire des choses vraiment très différentes avec le même événement. Dans ce tutoriel nous allons donc voir comment utiliser les événements, puis voir quelques exemples d'utilisations. Pour faire ce que vous souhaitez avec les événements, vous allez donc avoir besoin de compétence en java (ce qui est plutôt logique vu que les mods minecraft sont codés en java). Si vous avez besoin d'aide, vous pouvez toujours expliquer votre problème dans la section support pour les moddeurs. Nous verrons plus en détails certains événements dans d'autres tutoriels quand ils sont nécessaire (exemple, nous verrons l'event bone meal dans le tutoriel sur les plantes ou les arbres, l'event bucket dans le tutoriel sur les fluides, etc ...).

    Pré-requis

    Code

    Enregistrer une classe avec des événement :

    Tout d'abord il faut savoir que depuis la 1.7 les événements se distingue en deux catégories : les événements de FML (package cpw.mods.fml.common.gameevent) et les événements de Forge (package net.minecraftforge.client.event.* et net.minecraftforge.event.*).
    Il y a donc deux méthodes pour enregistrer une classe d'événements.

            FMLCommonHandler.instance().bus().register(new ClasseAvecTousVosEvent());
    

    Pour enregistrer une classe avec des événements FML et :

            MinecraftForge.EVENT_BUS.register(new ClasseAvecTousVosEvent());
    

    Pour enregistrer une classe avec des événements Forge.
    Le nom de la classe importe peu, mais pour une question d'organisation, si vous avez peu d'événement dans votre mod, faite simplement une classe EventHandler. Si vous avez beaucoup d'événement, il est sûrement de créer une classe par une catégorie (il faut donc enregistrer toutes les classes où il y a des événements avec la fonction s'y dessus). Par exemple, une classe LivingEventHandler pour tous les événements qui concerne le entité vivante, PlayerEventHandler pour tous les événements du joueur, etc ...

    Le code se place dans votre classe principale, dans la fonction init. D'ailleurs il est possible d'avoir des événements Forge et FML dans la même classe, simplement en enregistrant la classe avec les deux événements.

            FMLCommonHandler.instance().bus().register(new ClasseCommunAuEventForgeEtFML());
            MinecraftForge.EVENT_BUS.register(new ClasseCommunAuEventForgeEtFML());
    

    Voila, il ne vous reste plu qu'à créer la classe.

    Petite astuce, les événements de Forge qui sont dans le package net.minecraftforge.client.event.* ne sont que utilisable en client, pour enregistrer la classe passé donc par les proxy ou alors utilisez :

            if(event.getSide().isClient())
            {
                MinecraftForge.EVENT_BUS.register(new EventEnSoloUniquement());
            }
    

    La fonction pour l'événement :

    Maintenant la classe créé et enregistré, il faut y placer les événements. Le principe est vraiment simple, il suffit de créer une fonction void, avec en argument que vous souhaitez utiliser. Sans oublier d'ajouter l'annotation @SubscribeEvent au dessus de la fonction puisque c'est elle qui permet à FML ou Forge de détecter que le void est un événement.
    Un petit exemple :

    package fr.minecraftforgefrance.tutoriel.common;
    
    import net.minecraftforge.event.entity.EntityJoinWorldEvent;
    import cpw.mods.fml.common.eventhandler.SubscribeEvent;
    
    public class EntityEventHandler
    {
        @SubscribeEvent
        public void onEntityJoin(EntityJoinWorldEvent event)
        {
    
        }
    }
    

    J'ai ici ma classe EntityEventHandler où je place tout mes événements qui concerne les entités. Comme il s'agit d'un événement de Forge, je l'ai enregistré dans la fonction init de ma classe principale avec la fonction de forge.
    Dedans, j'ai bien une fonction du type void, avec l'annotation @SubscribeEvent juste au dessus, et en argument j'ai la classe de l'événement. Le nom de la fonction n'affecte absolument pas l'effet obtenu.

        @SubscribeEvent
        public void eventDemoParMinecraftForgeFrance(EntityJoinWorldEvent event)
        {
    
        }
    

    Ce code fonctionne tout aussi bien. Mais par principe, on met comme nom de fonction quelque chose qui correspond à son effet.
    Avec un minimum d'anglais, on devine que EntityJoinWorldEvent est déclenché lorsqu'une entité rejoint le monde. Pour l'instant le code ne fait rien, puisque je n'ai rien mit dedans. Mais si j'ajoute :

            System.out.println("une entité a rejoint le monde");
    

    Dans les logs, ce message apparaîtra dès qu'une entité rejoindra le monde. Un peu plus poussé :

            System.out.println("une entité (" + event.entity.getCommandSenderName() + ") a rejoint le monde");
    

    Maintenant dans les logs, ce message apparaîtra dès qu'une entité rejoindra le monde, mais en plus son nom sera indiqué entre parenthèse.
    Vous allez dire que ça n'a aucun intérêt, puisque afficher quelque chose dans la console ne sert à rien. Et en effet, ça ne sert à rien en soit, ce code est juste là pour vous expliquez le principe. Mais vous avez sûrement remarquez que dans le deuxième code j'ai utilisé event.entity. En effet, en fonction de l’événement que vous utilisez, vous allez avoir un certains nombres d'arguments. Tapez event. et appuyez sur la touche espace, vous allez voir tous les arguments disponibles. Dans cette événement, il y a deux arguments, entity et world. Et avec ces arguments, on peut faire des choses plus poussé. Par exemple :

        @SubscribeEvent
        public void onEntityJoin(EntityJoinWorldEvent event)
        {
            if(event.entity instanceof EntityZombie)
            {
                event.setCanceled(true);
            }
        }
    

    Avec ce code, si l'entité qui vient de rejoindre le monde est d'instance EntityZombie (donc les zombies et tous les mobs extends EntityZombie), j'annule l'événement. Annuler un événement sert à bloquer l'action. Dans le cas de cette exemple, les zombies (et autres mobs extends EntityZombie) ne pourrons plus spawner.
    /!\ Tous les événements ne peuvent pas être annulé. Pour savoir si l'événement que vous utilisez peut être annulé ou pas, faite un ctrl + clic sur la nom de la classe pour l'ouvrir, si vous voyez l'annotation @Cancelable, il peut être annulé. Dans le cas contraire, il ne peut pas être annulé. Sinon tester juste, si vous essayer d'annuler un événement qui ne peut pas être annulé, le jeu va crasher. /!\

    Certains événement on aussi l'annotation @HasResult, qui dans certains cas à le même effet que @Cancelable, par exemple pour la plupart des événements lié à la génération, si vous faite un

                event.setResult(Event.Result.DENY);
    

    (importez la classe Event de FML), la génération va se stoper.
    Dans d'autres cas, il sert pour appliquer l'effet, par exemple pour l'événement de la houe ou de la poudre d'os, mettre :

                event.setResult(Event.Result.ALLOW);
    

    sert à user la houe / réduire la quantité de poudre d'os et à faire l'animation du clic droit.
    Pour voir si un événement à un résultat, tout comme pour savoir si il peut être annulé, faite un ctrl + clic sur la classe et regardez les annotations que l'événement a.

    Quelques conseils :
    En général le nom des variables (c'est ce qui s'affiche quand vous faite event. + ctrl + espace) est toujours claire, si vous n'arrivez pas à déterminer à quoi sert une variable dans un événement, lisez la java doc, elle est assez remplit pour les événements.
    Le nom des événements est aussi toujours très claire, mais si vous souhaitez remontez à l'endroit où est déclenché l'événement, voici une petite astuce :

    • Sélectionnez le nom de la classe de l'événement
    • Ouvrez le menu Search d'eclipse, puis cliquez sur Java...
    • Comme vous avez sélectionnez le nom de la classe avant, la case de recherche devrait être déjà pleine, lancez la recherche
    • Il y a maintenant deux possibilités, soit vous tombez sur la classe ou l'événement est déclenché, soit vous tombez dans une classe du type ForgeHook ou FMLCommonHandler
    • Dans le premier cas, vous êtes là ou l'événement est déclenché, dans le second cas il faut simplement faire une second recherche de la fonction sur laquelle vous êtes tombé.
      Remonter à l'endroit où l'événement est déclenché peut parfois aider à comprendre à quoi sert l'événement.
      Lorsque vous cherchez un événement, ouvrez bien les classes, souvent il y a plusieurs événement dans une classe.

    Voila, vous savez maintenant tout ce qu'il y a à savoir sur les événements, je peux vraiment expliquer plus puisque sinon ça va être du cas par cas. Seule vos connaissances en java et votre imagination vont permettre d'utiliser les événements. Pour ceux qui ont du mal, place aux exemples !

    Quelques exemples d'utilisations :

    Je vais montrer ici les codes que j'utilise pour mes mods et les expliquer. Au moins cela vous donnes un exemple concret, utile, contrairement à mon exemple au dessus qui ne sert qu'à désactiver le spawn d'un mob.

    Dans un de mes mods actuellement en dévelopement, j'utilise BreakEvent pour rendre mon bloc destructible que par le propriétaire du bloc :

    package fr.mcnanotech.privatizer.common;
    
    import net.minecraft.tileentity.TileEntity;
    import net.minecraft.util.ChatComponentText;
    import net.minecraftforge.event.world.BlockEvent;
    import cpw.mods.fml.common.eventhandler.SubscribeEvent;
    
    public class PrivatizerEventHandler
    {
        @SubscribeEvent
        public void onBlockDestroyed(BlockEvent.BreakEvent event)
        {
            if(event.block instanceof BlockPrivate) // Vérifie si le bloc détruit est un bloc privé
            {
                TileEntity te = event.world.getTileEntity(event.x, event.y, event.z); // Obtient le tile entité du bloc
                if(te != null && te instanceof TileEntityPrivate) // Si il n'est pas null est qu'il est d'instance TileEntityPrivate
                {
                    TileEntityPrivate tePrivate = (TileEntityPrivate)te; // Je cast TileEntityPrivate à l'objet pour accéder au fonction de mon tile entity
                    if(!PrivatizerHelper.canBreak(event.getPlayer().getCommandSenderName(), tePrivate.getOwner())) // (Petite fonction pour vérifie que le joueur peut détruire le bloc passant par une autre classe) dans le cas où il ne peut pas détruire le bloc, alors
                    {
                        event.getPlayer().addChatMessage(new ChatComponentText("You can't remove this block, the owner is : " + tePrivate.getOwner())); // envoie un message au joueur
                        event.setCanceled(true); // annule l'événement, le bloc ne se casse pas.
                    }
                }
            }
        }
    }
    

    Il s'agit d'un événement de forge, dans ma classe principale il est donc enregistré avec MinecraftForge.EVENT_BUS.register(new PrivatizerEventHandler());
    Pour les intéressé, lien du projet github : https://github.com/FFMT/Privatizer

    Un autre code qui m'a souvent été demandé :

        @SubscribeEvent
        public void onTickClient(TickEvent.ClientTickEvent event)
        {
            if(event.phase == Phase.START)
            {
                Minecraft mc = Minecraft.getMinecraft();
                GuiScreen currentScreen = mc.currentScreen;
                GuiCustomMainMenu customMenu = new GuiCustomMainMenu();
    
                if(currentScreen instanceof GuiMainMenu && !currentScreen.equals(customMenu))
                {
                    mc.displayGuiScreen(customMenu);
                }
            }
        }
    }
    

    Il permet de remplacer le menu principale par son propre menu. Attention, à faire côté client seulement ! Et il s'agit d'un événement de FML, donc à enregistrer avec :

            if(event.getSide().isClient())
            {
                FMLCommonHandler.instance().bus().register(new LaClasseOuSeTrouveVotreEvent());
            }
    

    À propos des événements fait en client seulement, il est aussi possible d'utiliser @SideOnly :

        @SubscribeEvent
        @SideOnly(Side.CLIENT)
        public void onTooltip(ItemTooltipEvent event)
        {
            if(event.itemStack.getItem() == Items.dye && event.itemStack.getItemDamage() == 15)
            {
                event.toolTip.add("clic droit sur les plantes pour les faire pousser");
            }
            else if(event.itemStack.getItem() == Item.getItemFromBlock(Blocks.obsidian))
            {
                if(Keyboard.isKeyDown(Keyboard.KEY_LSHIFT))
                {
                    event.toolTip.add("Bloc très résistant aux exposions");
                    event.toolTip.add("Résistance : 2000");
                }
                else
                {
                    event.toolTip.add("Maintenir shift gauche pour plus d'information");
                }
            }
        }
    

    Même si je mets cette fonction dans un, événement enregistré en client et en serveur, le jeu ne crachera pas avec un NoClassDefFound : org.lwjgl.input.Keyboard puisqu'en serveur il ne lira pas ce code. Ce code permet d'afficher du texte en dessous de d'un item / bloc lorsqu'on passe la souris dessus. L'astuce du "Maintenir shift gauche pour plus d'information" peut être très utile si vous beaucoup de ligne d'information.
    Item.getItemFromBlock(objet du bloc) est un fonction très importante qui sert à chaque fois que avez besoin de comparer un bloc avec un item.

    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 :

    Creative Commons
    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

    retourRetour vers le sommaire des tutoriels



  • Une petite question, comment faire ceci:

    En 1.6.4 ?


  • Administrateurs

    Même chose, sauf qu'il faut comparer les id.

    if(event.itemStack.itemID == Item.dye.itemID && event.itemStack.getItemDamage() == 15)
    


  • Merci c'est bon !



  • C'est possible de rendre "plus rapide" les évents ?

    Car je viens de créer un GuiMainMenu perso (et un Ingame), j'ai utilisé la méthode citée dans ton poste, mais ça donne moyen, on voit tout de même apparaître le menu "par défaut" :

    0_1529750411039_175051menu.gif


  • Administrateurs

    Non, pas possible.



  • @'robin4002':

    Non, pas possible.

    Oh mon dieu, Robin a trouvé quelque chose d'impossible à faire avec Forge 😮



  • pour ma part sa marche pas ! il pige pas @SubscribeEvent donc j'ai mi à la place @SubscribeEvent ce qui enleve l'erreure mais sa marche pas jai pas l'effet


  • Administrateurs

    Heu ? Pas tout compris, envoie ton code. Tu code en quelle version ?



  • Salut, oui c'est encore moi ^^ Je ne trouves pas d'event pour quand on drop, j'ai pour quand on pickup mais pas drop..

    Et je voulais savoir, si cet event existe serait-il possible de faire que si c'est dropper sur un block, ici pour moi un chaudron, beh ca "l'avale" et ca ajoute une valeur a une variable, et que quand cette variable est égal a un nombre, alors ca give un item et ca reset la variable, comme ca je pourrais faire un systeme pour créer des potions! 🙂 ^^ Merci de votre aide!

    Cordialement Sosoh


  • Administrateurs

    Regarde le code du Hopper



  • Oh, mais je crois que je suis débile! Je peux faire un GUI, mais je vais regarder quand même du coté hopper, merci.

    Edit: Ils use des NBT tag, mais je sais pas trop comment donc je penses que je vais faire un GUI, mais si quelqu'un sait comment faire je suis ok!


  • Administrateurs

    Et sinon tu as PlayerTossItem event il me semble pour quand le joueur lâche un item (ou un truc du genre).



  • Toss en anglais ça veut dire tirage au sort, donc je ne sais pas..


  • Administrateurs

    Faut pas s'arrêter à la première traduction :

    net.minecraftforge.event.entity.item.ItemTossEvent



  • Ah oui, je me suis lancer dans le gui donc je verrais apres.. ^^



  • Il existe tu un event qui permet de modifier une class de minecraft, par exemple comme avec les GuiMenu

    @SubscribeEvent
    public void onTickClient(TickEvent.ClientTickEvent event)
    {
        if(event.phase == Phase.START)
        {
            Minecraft mc = Minecraft.getMinecraft();
            GuiScreen currentScreen = mc.currentScreen;
            GuiCustomMainMenu customMenu = new GuiCustomMainMenu();
    
            if(currentScreen instanceof GuiMainMenu && !currentScreen.equals(customMenu))
            {
                mc.displayGuiScreen(customMenu);
            }
        }
    }
    

    C'est pour modifier ma class ItemArmor afin de réduire tout les armures par Default de minecraft et aussi pour autre chose.


  • Administrateurs

    Non, il n'existe pas d'event pour ça.



  • Merci ta réponse mais il existe surement un moyen chargé les armures de résistance par Default ? 
    Car quand je rajoute plusieurs armures et que je montre leurs résistance les armures se détruit a moitié de leurs capacité.


  • Administrateurs

    heu comment ça ?