Utiliser les événements
-
Lors du développement de la version 2.0 du mod Buildcraft, SpaceToad a voulu ajouter du pétrole. Or, 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. C’est ainsi que Forge a été créé. En effet, la fonctionnalité pour les seaux était la première de Forge (le 4ème commit sur github)
Depuis, un système d’événement a été mis en place de façon plus optimisée. Les événements font donc partie des fonctionnalités les plus importantes de Forge, puisqu’ils permettent de faire des choses qui serait normalement impossibles sans modifier les classes de Minecraft. De plus, dans les versions récentes, ils sont présents presque partout.Une liste des différents événements est disponible à cette adresse (liste non-exhaustive, car elle n’est pas mise à jour systématiquement).
Sommaire du tutoriel
- Pré-requis
- Principe de fonctionnement
- Souscrire à un événement
- Exemples d’utilisation
- Résultat
- Licence et attribution
Pré-requis
Principe de fonctionnement
Le principe des événements est simple : à un endroit du code de Minecraft, Forge va déclencher un événement associé. Les mods vont pouvoir souscrire à ces événements à l’aide d’une méthode, qui sera donc appelée lors du déclenchement de ce dernier, ce qui permet alors au mod d’effectuer diverses actions. Certains événements peuvent être annulés et, dans ce cas, la logique de Minecraft associée ne sera pas effectuée.
Les événements disposent d’un système de priorité, un mod écoutant un événement en
EventPriority.HIGHEST
verra sa méthode appelée avant celle d’un mod ne précisant rien (NORMAL
par défaut). Les priorités sont les suivantes :HIGHEST, // la première a être exécuté HIGH, NORMAL, LOW, LOWEST; // la dernière a être exécuté
Note
Il existe aussi plusieurs bus.
MinecraftForge.EVENT_BUS
est le bus sur lequel la presque totalité des événements sont déclenchés, c’est principalement sur celui qu’il faudra souscrire. Il existe égalementFMLJavaModLoadingContext.get().getModEventBus()
qui est le bus spécifique au mod Java. Il est utilisé pour tout ce qui touche au registre (événements d’enregistrement d’entité, de bloc, d’item, etc.).Souscrire à un événement
La souscription à un événement passera toujours par une méthode prenant en argument l’événement en question (et aucun autre argument). Cette méthode peut avoir n’importe quel nom, seul le premier paramètre a son importance :
public void onEntityDeath(LivingDeathEvent event) { // ok } public void myMethod(LivingDeathEvent event) { // ok } public void onEntityDeath(LivingDeathEvent event, EntityPlayer player) { // MAUVAIS ! Seul un événement peut être mis en argument ! }
Ensuite, en fonction de si votre méthode est statique ou non, 2 ou 3 options se présentent pour enregistrer la méthode. La première permet d’enregistrer les méthodes une par une, tandis que les deux autres indiquent qu’une classe entière contient des méthodes d’événements.
Méthode statique
L’enregistrement de fonction au cas par cas se fait en appelant la méthode addListener du bus voulu, suivie de la priorité (optionnelle) et de la méthode sous la forme
NomDeLaClasse::nomDeLaMethode
:MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGH, ModTutorial::onEntityDeath);
Et la méthode d’événement associée :
public static void onEntityDeath(LivingDeathEvent event) { // instructions ... }
La seconde façon permet d’enregistrer une classe entière contenant des événements :
MinecraftForge.EVENT_BUS.register(ModTutorialEvents.class);
Grace à cet enregistrement, Forge va chercher toutes les fonctions d’événements de la classe
ModTutorialEvents
. Encore faut-il lui indiquer lesquels il s’agit. Pour cela il faut utiliser l’annotation@SubscribeEvent
:@SubscribeEvent public static void onEntityDeath(LivingDeathEvent event) { // instructions ... } @SubscribeEvent(priority = EventPriority.HIGHEST) // on peut également préciser la priorité comme cela public static void onEntityAttack(LivingAttackEvent event) { // instructions ... }
Mais dans certains cas, nous n’avons pas la possibilité d’utiliser
addListener
niregister
car certains événements sont appelés avant même que la classe principale du mod soit construite. On peut alors utiliser l’annotation de classe@EventBusSubscriber
:package dev.mff.modtutorial; import net.minecraftforge.event.entity.living.LivingDeathEvent; import net.minecraftforge.eventbus.api.EventPriority; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; @EventBusSubscriber(modid = ModTutorial.MOD_ID) public class ModTutorialEvents { @SubscribeEvent(priority = EventPriority.HIGHEST) public static void onEntityDeath(LivingDeathEvent event) { // instructions ... } }
Celle-ci a exactement le même effet qu’utiliser la fonction register sur une classe. La syntaxe peut simplement être plus pratique et permet d’enregistrer une classe d’événement dès sa découverte par Forge et donc avant l’instanciation de la classe principale.
Par défaut, @EventBusSubscriber enregistre avec le bus de Forge.EVENT_BUS, mais on peut également le changer (nécessaire pour l’enregistrement de bloc, item, entité, etc) :
@EventBusSubscriber(modid = ModTutorial.MOD_ID, bus = Bus.MOD)
On peut aussi cibler une distribution (client ou serveur dédié, par défaut ce sont les deux). Cela peut se montrer utile pour les événements étant présents que sur une distribution, par exemple les événements de rendu qui ne sont que sur le client :
@EventBusSubscriber(modid = ModTutorial.MOD_ID, value = Dist.CLIENT) public class ModTutorialEvents { @SubscribeEvent public static void onOverlay(RenderGameOverlayEvent.Text event) { event.getLeft().add("Test d'overlay"); } }
Méthode non statique
Comme avant, on peut enregistrer les méthodes une par une. Seule la façon de préciser la méthode diffère : au lieu d’indiquer la classe, on met l’instance de la classe :
MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGH, this::onEntityDeath);
Et la méthode d’événement associée, qui devra donc se trouver dans la même classe :
public void onEntityDeath(LivingDeathEvent event) { // instructions ... }
La deuxième méthode passe par la fonction
register
et, à nouveau, seul l’argument diffère :MinecraftForge.EVENT_BUS.register(new ModTutorialEvents());
Les méthodes d’événements devront également avoir l’annotation
@SubscribeEvent
comme vu précédemment. Pensez, par contre, à ne pas mettre le mot cléstatic
dans ce cas.Naturellement, il n’y a pas d’équivalent à
EventBusSubscriber
pour les méthodes non statiques, Forge ne sachant pas comment instancier votre classe contenant les événements.Exemples d’utilisation
Les possibilités offertes par les différents événements étant trop nombreuses, il n’est pas possible de toutes les présenter dans un tutoriel. Afin que ce tutoriel ne soit pas trop abstrait et uniquement théorique, je vous propose quelques exemples d’utilisations d’événements.
Modifier le comportement des explosions
Forge propose deux événements en rapport avec les explosions,
ExplosionEvent.Start
déclenché avant l’explosion ce qui permet de l’annuler etExplosionEvent.Detonate
qui est déclenché après avoir calculé les dégâts et avant de les annuler. Pour cet exemple le second sera utilisé, le but ici étant de modifier le comportement de l’explosion en excluant un bloc (les lampes) et les creepers.public void onExplosion(ExplosionEvent.Detonate event) { event.getAffectedBlocks().removeIf(pos -> event.getWorld().getBlockState(pos).getBlock() == Blocks.REDSTONE_LAMP); event.getAffectedEntities().removeIf(e -> e instanceof EntityCreeper); }
L’événement nous permet d’accéder à la liste des blocs (liste de
BlockPos
) et des entités affectés et avec la fonctionremoveIf
des listes de Java 8 on peut facilement retirer un élément s’il respecte une condition.
On peut trouver plein d’autres utilités à cet événement, comme logger la liste des blocs détruits, empêcher la destruction de blocs dans une zone donnée, etc.Note
Cet événement a été enregistré en utilisant
MinecraftForge.EVENT_BUS.addListener(EventPriority.HIGH, this::onExplosion);
Modifier la croissance des plantes
Certains événements ont un résultat, ils sont remarquables grâce à l’annotation
@HasResult
. C’est par exemple le cas deBlockEvent.CropGrowEvent.Pre
. La javadoc nous indique qu’un résultatDEFAULT
garde le comportement normal,ALLOW
force la croissance de la plante etDENY
l’empêche.
Rendons la croissance des cactus impossible hormis dans le désert :public void onCropGrow(BlockEvent.CropGrowEvent.Pre event) { if (event.getState().getBlock() == Blocks.CACTUS && event.getWorld().getBiome(event.getPos()) != Biomes.DESERT) { event.setResult(Result.DENY); } }
(L’événement dans cet exemple est enregistré comme le précédent, d’où l’absence de l’annotation
@SubscribeEvent
).Texte sur le HUD et message de chat
Certains événements ne sont que présents sur le client. Ils sont facilement identifiables par le fait qu’ils se trouvent en général dans le package
net.minecraftforge.client.event
. Ils concernent souvent le rendu, l’affichage d’éléments ou la gestion des entrées clavier / souris.RenderGameOverlayEvent
, par exemple est un événement client qui est déclenché lors du rendu du HUD de Minecraft et dispose de plusieurs sous-classes (.Pre
déclenché avant le rendu de l’overlay,.Post
pour après et.Text
pour l’affichage de texte). Ici, il va être utilisé pour afficher un texte en haut à gauche du HUD.Afin de vous montrer un exemple d’événement annulé, je vous propose aussi un exemple d’utilisation de
ClientChatReceivedEvent
, qui est déclenché lorsque le client reçoit un message de chat, juste avant de l’afficher. L’exemple qui suit sert à empêcher l’affichage du message de connexion d’un joueur au serveur.package dev.mff.modtutorial; import net.minecraft.util.text.TextComponentTranslation; import net.minecraftforge.api.distmarker.Dist; import net.minecraftforge.api.distmarker.OnlyIn; import net.minecraftforge.client.event.ClientChatReceivedEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent; import net.minecraftforge.client.event.RenderGameOverlayEvent.ElementType; import net.minecraftforge.eventbus.api.SubscribeEvent; import net.minecraftforge.fml.common.Mod.EventBusSubscriber; @EventBusSubscriber(modid = ModTutorial.MOD_ID, value = Dist.CLIENT) @OnlyIn(Dist.CLIENT) public class ModTutorialClientEvents { @SubscribeEvent public static void onOverlay(RenderGameOverlayEvent.Text event) { event.getLeft().add("MFF"); } @SubscribeEvent public static void onMessage(ClientChatReceivedEvent event) { if (event.getMessage() instanceof TextComponentTranslation) { // si le message est un texte de traduction TextComponentTranslation translation = (TextComponentTranslation) event.getMessage(); if (translation.getKey().equals("multiplayer.player.joined")) { // et qu'il s'agit du message de connexion event.setCanceled(true); // on annule, le message ne s'affichera donc pas dans le chat } } } }
Note
Pour savoir si un événement peut être annulé, il suffit de regarder s’il a l’annotation
@Cancelable
. Appeler la fonctionevent.setCanceled
sur un événement qui ne peut pas être annulé causera un crash.Résultat
Les cactus ne poussent plus, la TNT ne détruit plus les lampes et MFF s’affiche en haut à gauche. Tout est bon !
Les différentes modifications du code sont retrouvables sur le commit Github suivant : https://github.com/MinecraftForgeFrance/mod-tutorial-1.13.2/commit/1a2e732cf4d9588737ff6467b53698a4b3f2ce8e
Licence et attribution
Ce tutoriel rédigé par @robin4002 corrigé par @BrokenSwing et @DiabolicaTrix et 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
-
-
-
-
-
-