Communiquer entre le client et le serveur : le réseau et les paquets
-
Comme beaucoup de jeux et de logiciels, Minecraft est séparé en deux parties : le client et le serveur. Dans de nombreux cas, ces deux parties ont besoin de communiquer afin d’échanger des informations. Afin d’y parvenir, celles-ci utilisent ce que l’on appelle des paquets et nous allons voir comment créer nos propres paquets afin d’échanger nos propres informations.
Rappel
Dans le cas de Minecraft, que le client soit connecté à un serveur ou bien qu’il joue en solo, cette architecture client / serveur est présente, il faut donc bien faire attention à cette séparation dans tous les cas.
Cas d’utilisation :
Cas n° 1 : Nous possédons un système d’argent par joueur et nous voulons afficher la somme que le joueur possède sur son écran. Alors, il nous faut connaître cette dernière du côté client afin d’être capable d’effectuer le rendu. Il faut donc que le serveur communique cette somme au client.
Cas n° 2 : Nous possédons une GUI (Graphical User Interface) avec des boutons et nous voulons être notifié côté serveur (rappelons qu’une GUI se situe côté client) afin d’effectuer une action associée à ce bouton. Il faut donc que le client communique cette information au serveur.
Important
La liste ci-dessus n’est pas une liste exhaustive des cas d’utilisation des paquets mais bien deux exemples parmi tant d’autres possibles.
Sommaire du tutoriel
- Fonctionnement du réseau
- Création d’un channel
- Création d’un paquet
- Enregistrement d’un paquet
- Envoyer un paquet
- Approfondissement
- Résultat sur Github
Fonctionnement du network
Si vous savez déjà comment fonctionne le système de paquets de Forge, vous pouvez passer cette partie.
Pour communiquer entre le client et le serveur, Minecraft utilise un framework appelé Netty, vous n’aurez cependant pas à utiliser ce framework directement car que ce soit Minecraft ou Forge, chacun possède une surcouche facilitant la communication entre notre client et notre serveur.
Afin de communiquer, il nous faudra faire transiter nos paquets via le réseau, pour cela nous devrons créer ce que l’on appelle des channels. Il faut voir un channel comme étant une route entre votre client et votre serveur sur laquelle vos paquets vont se déplacer, on peut avoir plusieurs routes entre un même client et le serveur. En pratique, on utilisera au minimum un channel par mod.
Pour ce qui est des paquets, il faut se les représenter comme un tas de données brutes (des octets) que l’on fait transiter via le réseau. Or, en Java, on utilise généralement des objets dans notre code. Afin de faire transiter nos objets, il nous faudra alors les sérialiser sous formes d’octets afin des les envoyer au client ou au serveur puis, arrivés là-bas il faudra les désérialiser afin de les retransformer en objets.
Afin d’appeler la bonne fonction de sérialisation et de désérialisation, Forge nous demande d’associer ensemble :
- Une nombre unique (appelé discriminant)
- Une classe (que l’on appellera
T
ici) - Une fonction de sérialisation pour la classe
T
- Une fonction de désérialisation pour la classe
T
- Une fonction qui sera appelée à la réception du paquet
Ainsi, lorsqu’on demande à Forge d’envoyer un certain objet via le réseau, il cherche dans la liste des associations que l’on lui a fourni, l’association pour la classe de l’objet donné. Ensuite, il appelle la fonction de sérialisation sur l’instance à envoyer afin de transformer l’objet en octets. Enfin, il rajoute le discriminant au début des octets.
Le tout est envoyé via le channel sur lequel est enregistré l’association.
À la réception du paquet, Forge récupère le discriminant et cherche dans les associations celle possédant le discriminant du paquet reçu, il appelle alors la fonction de désérialisation afin de récupérer une instance de l’objet similaire à celle envoyée, puis passe cet objet à la fonction gérant la réception du paquet.
Retenir
Les associations telles que présentées ci-dessus sont enregistrées spécifiquement sur un channel. Il faut donc envoyer l’objet en utilisant le channel sur lequel on a enregistré l’association qui gérera ce paquet.
Création d’un channel
Nous allons tout d’abord créer une classe qui regroupera tout ce qui concerne le réseau (création des channels et enregistrement des paquets).
Pour ma part je vais l’appelerTutorialNetwork
. Le nom a peu d’importance, du moment que vous vous y retrouvez par la suite.
Avant toute chose, nous allons avoir besoin de spécifier la version de notre channel, c’est une chaîne de caractères qui n’a de sens que pour vous (il est nécessaire de l’indiquer, mais vous ne vous en préoccuperez pas en réalité).
Créons donc un champ contenant la version, dans mon cas il contiendra la valeur1
.public static final String PROTOCOL_VERSION = String.valueOf(1);
Ensuite nous allons ajouter notre channel . La classe du channel est
SimpleChannel
et nous allons utiliser le builderNetworkRegistry.ChannelBuilder
pour l’instancier :public static final SimpleChannel CHANNEL = NetworkRegistry.ChannelBuilder .named(new ResourceLocation(ModTutorial.MOD_ID, "my_channel")) .networkProtocolVersion(() -> PROTOCOL_VERSION) .clientAcceptedVersions(PROTOCOL_VERSION::equals) .serverAcceptedVersions(PROTOCOL_VERSION::equals) .simpleChannel();
Voyons un peu ce que nous venons de faire ici :
-
Ligne 2 :
.named
prend uneResourceLocation
représentant le nom de votre channel. Mettez l’id de votre mod en domaine (ici c’est monModTutorial.MOD_ID
) puis ce que vous voulez pour le chemin (ici monmy_channel
). -
Ligne 3 :
.networkProtocolVersion
prend unProvider<String>
qui doit renvoyer la version de votre protocol. Ici nous renvoyons le champ que nous avons créé plus haut. -
Lignes 4 et 5 : celles-ci demandent un
Predicate<String>
afin de vérifier que la version sur le côté client et sur le côté serveur sont compatibles. Dans notre cas nous vérifions simplement que la version correspond à la version actuelle de notre protocol. -
Ligne 6 :
.simpleChannel()
finalise la création de notre channel et nous renvoie son instance.
Création d’un paquet
Comme expliqué précédemment, un paquet est représenté par une classe, nous allons donc en créer une. Pour ma part, je l’appellerai
MySimplePacket
. Pour commencer, je ne vais y mettre qu’une valeur d’entier comme attribut :public class MySimplePacket { private int value; public MySimplePacket(int value) { this.value = value; } }
Nous allons ensuite créer les fonctions de sérialisation et de désérialisation. Leur nom importe peu, mais :
-
La fonction de sérialisation doit prendre en paramètre l’objet à sérialiser (ici une instance de
MySimplePacket
et unPacketBuffer
dans lequel on stockera l’objet sous forme d’octets. Elle ne doit rien retourner. -
La fonction de désérialisation doit prendre en paramètre un
PacketBuffer
et retourner l’instance de l’objet désérialisé (ici une instance deMySimplePacket
).
Fonction de sérialisation :
public static void encode(MySimplePacket packet, PacketBuffer buffer) { buffer.writeInt(packet.value); }
Dans cette fonction j’écris donc l’entier de mon paquet dans le
PacketBuffer
.Fonction de désérialisation :
public static MySimplePacket decode(PacketBuffer buffer) { int value = buffer.readInt(); MySimplePacket instance = new MySimplePacket(value); return instance; }
Et dans cette fonction, je récupère la valeur de mon entier dans le
PacketBuffer
puis la passe en paramètre au constructeur de mon objet que je retourne ensuite. NB : J’aurais pu directement mettrereturn new MySimplePacket(buffer.readInt())
.Indication
Le contenu du
PacketBuffer
que vous remplissez dans la fonction de sérialisation est le même que celui qui vous est donné dans la fonction de désérialisation. L’écriture et la lecture de données dans lePacketBuffer
seront abordées plus en détails plus tard.Attention
Le
PacketBuffer
représentant une suite d’octets, vous devez lire les valeurs dans le même ordre que celui dans lequel vous les avez écrites.Si vous avez suivi correctement le tutoriel jusqu’à maintenant vous devriez savoir qu’il ne reste plus qu’à créer la fonction qui gérera la réception de votre paquet. Cette fonction prend en paramètres une instance de votre paquet (ici une instance de
MySimplePacket
) et unSupplier<NetworkEvent.Context>
et ne renvoie rien. Ce second argument permet de récupérer le contexte dans lequel le paquet a été envoyé. Il permet, entre autres, de récupérer l’instance du joueur côté serveur ou encore de savoir si nous sommes côté client ou côté serveur.Ajoutons donc cette fonction à notre classe :
public static void handle(MySimplePacket packet, Supplier<NetworkEvent.Context> ctx) { System.out.println(packet.value); ctx.get().setPacketHandled(true); }
Ici, j’affiche donc la valeur de mon paquet, puis j’appelle
NetworkEvent.Context#setPacketHandled
afin de dire que j’ai géré le paquet. Il est nécessaire de dire que nous avons géré le paquet sinon ce dernier est envoyé dans le code de Minecraft qui gère les paquets et affiche un message d’erreur.Nous sommes maintenant prêts à enregistrer notre paquet tout neuf sur notre channel.
Enregistrement d’un paquet
De retour dans notre classe
TutorialNetwork
. Nous allons créer une fonction dans laquelle nous enregistrerons nos paquets. Pour ma part, je vais l’appelerregisterNetworkPackets
. Ensuite, nous allons utiliserSimpleChannel#messageBuilder
pour enregistrer notre paquet :public static void registerNetworkPackets() { CHANNEL.messageBuilder(MySimplePacket.class, 0) .encoder(MySimplePacket::encode) .decoder(MySimplePacket::decode) .consumer(MySimplePacket::handle) .add(); }
Le code précédent ne devrait pas être surprenant et vous devriez facilement faire le parallèle avec les associations dont je vous ai parlé auparavant. Mais analysons-le quand même :
-
Ligne 3 :
.messageBuilder
prend en paramètres la classe de votre paquet ainsi que le discriminant (cf. 1ère partie du tuto). -
Ligne 4 :
.encoder
prend en paramètre la fonction de sérialisation. -
Ligne 5 :
.decoder
prend en paramètre la fonction de désérialisation. -
Ligne 6 :
.consumer
prend en paramètre le fonction qui va gérer votre paquet à sa réception. -
Ligne 7:
.add
permet de finaliser l’enregistrement.
Important
Le discriminant doit être différent pour chaque enregistrement de paquet.
Information
Lors de l’enregistrement, vous verrez sûrement que le builder possède d’autres méthodes, cependant ne vous en préoccupez pas car elles ne sont utiles que pour Forge.
Il suffit maintenant d’appeler la fonction
registerNetworkPackets
dans la fonction setup de votre mod (celle prenant en paramètreFMLCommonSetupEvent
).public ModTutorial() { // [...] FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup); // [...] } private void setup(final FMLCommonSetupEvent event) { // [...] TutorialNetwork.registerNetworkPackets(); // [...] }
Envoyer un paquet
Pour envoyer votre paquet il suffit maintenant d’appeler
SimpleChannel#send
pour le faire transiter du client au serveur ou du serveur à un ou des clients. Il vous faudra aussi vous aider des champs déclarés dans la classePacketDistributor
, je vous donne quelques exemples ci-dessous.Attention
Vous ne pouvez pas envoyer un paquet d’un client à un autre, il faut forcément passer par le serveur !
- Exemple d’envoi de notre paquet sur le serveur :
Les deux lignes de code ci-dessous parviennent au même résultat.
// Affichera la valeur 12 dans la console du serveur : // 1ère solution TutorialNetwork.CHANNEL.sendToServer(new MySimplePacket(12)); // 2nd solution TutorialNetwork.CHANNEL.send(PacketDistributor.SERVER.noArg(), new MySimplePacket(12));
- Exemple d’envoi de notre paquet sur le client :
Pour envoyer un paquet à un client, il faut avoir une instance deEntityPlayerMP
.
public static void sendTo(EntityPlayerMP player) { // Affiche 10 dans la console du client TutorialNetwork.CHANNEL.send(PacketDistributor.PLAYER.with(() -> player), new MySimplePacket(10)); }
- Exemple d’envoi d’un paquet à tous les clients du serveur :
// Affiche 42 dans la console de tous les clients TutorialNetwork.CHANNEL.send(PacketDistributor.ALL.noArg(), new MySimplePacket(42));
- Exemple d’envoi d’un paquet à tous les clients se trouvant dans une dimension donnée :
// Affiche 36 dans la console de tous les joueurs se trouvant dans le nether TutorialNetwork.CHANNEL.send(PacketDistributor.DIMENSION.with(() -> DimensionType.NETHER), new MySimplePacket(36));
Voilà pour les exemples, n’hésitez pas à regarder la classe
PacketDistributor
pour découvrir les autres filtres d’envoi.Important
Vous pouvez envoyer un paquet depuis n’importe quelle partie de votre code.
Approfondissement
J’ai volontairement survolé quelques points dans les sections précédentes car ce n’était pas forcément le moment d’en parler, mais ce moment est arrivé.
- Sérialiser/Désérialiser vos objets :
La sérialisation et la désérialisation de vos objets est une étape importante la création de votre paquet. Il va en effet généralement vous falloir découper votre objets en des objets ou type primitifs facilement sérialisables (
String
,int
,boolean
,UUID
, etc …).
Je ne vais pas faire l’inventaire des types facilement sérialisables via lePacketBuffer
, pour cela il vous suffit de chercher toutes les méthodes qui commencent parwrite
dans cette classe.
Je vais cependant donner un exemple de sérialisation d’une collection d’objets de la classe suivante :public class MyComplexObject { private String foo; private int bar; private UUID foobar; private ItemStack stack; }
Dans un premier temps je vais écrire une méthode qui va sérialiser une instance de
MyComplexObject
dans unPacketBuffer
:public void writeToBuffer(PacketBuffer buffer) { buffer.writeString(this.foo); buffer.writeInt(this.bar); buffer.writeUniqueId(this.foobar); buffer.writeItemStack(this.stack); }
Ensuite, je vais écrire une méthode qui va désérialiser cet objet. Pour ce faire, je vais créer un constructeur qui prend un
PacketBuffer
en entrée :public MyComplexObject(PacketBuffer buffer) { this.foo = buffer.readString(32767); // 32767 correspond à la longueur max de la chaine de caractères this.bar = buffer.readInt(); this.foobar = buffer.readUniqueId(); this.stack = buffer.readItemStack(); }
On rappelle qu’il faut lire les données dans le même ordre qu’elles ont été écrites.
Une fois tout cela fait, je crée mon paquet :public class MyComplexPacket { private Collection<MyComplexObject> myCollection; public MyComplexPacket(Collection<MyComplexObject> collection) { this.myCollection = collection; } }
Puis je crée les fonctions de sérialisation et de désérialisation du paquet :
public static void encode(MyComplexPacket pck, PacketBuffer buf) { // J'écris le nombre d'objet que je vais sérialiser buf.writeInt(pck.myCollection.size()); // Puis je sérialiser chaque objet pck.myCollection.forEach(o -> o.writeToBuffer(buf)); } public static MyComplexPacket decode(PacketBuffer buffer) { // Je lis combien d'objet on été sérialisés int collectionSize = buffer.readInt(); // Puis je lis autant d'objet qui ont été sérialisés Collection<MyComplexObject> complexObjects = Stream .generate(() -> new MyComplexObject(buffer)) .limit(collectionSize) .collect(Collectors.toList()); // Enfin, je retourne l'instance de mon paquet return new MyComplexPacket(complexObjects); }
Et voilà, vous savez maintenant sérialiser et désérialiser une collection d’objets.
- Quelques informations sur le
NetworkEvent.Context
:
Comme vu précédemment, lorsque l’on gère l’arrivé d’un paquet, on nous met à disposition un
Supplier<NetworkEvent.Context>
. Ici, nous allons voir légèrement plus en profondeur ce que ce dernier nous donne comme informations.Dans un premier temps, vous pouvez tout simplement récupérer le contexte dans lequel le paquet a été reçu en appelant
Supplier#get
qui va vous retourner une instance deNetworkEvent.Context
, c’est celui-ci qui nous intéresse bien sûr.-
Context#getDirection
vous permet de récupérer une énumération indiquant vers où le paquet a été envoyé.NetworkDirection#PLAY_TO_SERVER
etNetworkDirection#LOGIN_TO_SERVER
pour client vers serveur,NetworkDirection#PLAY_TO_CLIENT
etNetworkDirection#LOGIN_TO_CLIENT
pour serveur vers client. La différence entrePLAY
etLOGIN
correspond au moment où le paquet est envoyé,PLAY
si l’entité du joueur a été créée,LOGIN
si l’entité n’a pas encore été créée et que le client est encore en phase de chargement. Vous ne devriez avoir à gérer que le premier cas (PLAY
). -
Context#enqueueWork
permet de définir un travail qui sera réalisé au prochain tick sur le thread du client ou du serveur (pas sur le thread de Netty). -
Context#getSender
renvoie une instance deEntityPlayerMP
correspondante au client qui a envoyé le paquet au serveur. Comme vous venez de le comprendre, celle-ci n’est utilisable que sur le serveur. -
Context#attr
permet d’accéder à un certain attribut du channel entre le serveur et le client. L’utilisation de cette fonctionnalité correspond à des cas particulier que vous ne rencontrerez sûrement pas, je ne vais donc pas m’étendre plus longtemps dessus (mais sachez que ça existe). -
SimpleChannel#reply(NetworkEvent.Context, MSG)
permet de renvoyer un message à celui qui vous en a envoyé un.
Je pense que nous avons maintenant fait le tour de ce qu’il y a à voir, j’espère que vous compris comment fonctionne le network et je vous souhaite du bon modding.
Résultat sur Github
Vous pouvez consulter le commit concernant ce tutoriel sur le Github de MinecraftForgeFrance.
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
-
Salut Broken,
merci pour ce tutoriel très complet !
J’ai cependant une question qui pourra peut-être aider plus d’un!
Pour un packet envoyé au client, comme tu le précises le Context#getSender ne fonctionne pas car nous sommes du côté client, il faudrait donc utiliser Minecraft#getInstance#player sauf que lors de l’enregistrement côté client aucun problème mais côté coté serveur la classe EntityPlayerSP n’est pas trouvé (ce qui est logique).
Comment faut-il donc procéder ? Car un OnlyIn(Dist.CLIENT) au dessus du handler ne fonctionne pas ?
Merci encore
-
Salut,
Tu as aussi le problème si tu mets le code utilisant EntityPlayerSP dans la fonction
ctx.get().enqueueWork(() -> { });
? -
Salut,
oui…
-
ctx.get().enqueueWork(new Runnable() { @Override @OnlyIn(Dist.CLIENT) public void run() { EntityPlayerSP player = Minecraft.getInstance().player; .... } });
Comme ça je pense que ça sera bon. Par contre c’est moins bien pour la lisibilité (pas possible d’appliquer l’annotation sur une lambda).
-
@robin4002 a dit dans Communiquer entre le client et le serveur : le réseau et les paquets :
ctx.get().enqueueWork(new Runnable() { @Override @OnlyIn(Dist.CLIENT) public void run() { EntityPlayerSP player = SGClient.MC.player; … } });
Toujours pas
Voici l’erreur
Index: 1 Listeners: 0: NORMAL 1: net.minecraftforge.eventbus.EventBus$$Lambda$1843/1994991750@5b112b8d java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: fr.fifoube.packets.PacketMoneyData.handle(Lfr/fifoube/packets/PacketMoneyData;Ljava/util/function/Supplier;)V at fr.fifoube.packets.PacketsRegistery.registerNetworkPackets(PacketsRegistery.java:18) at fr.fifoube.main.ModEconomyInc.setup(ModEconomyInc.java:74) at net.minecraftforge.eventbus.EventBus.doCastFilter(EventBus.java:209) at net.minecraftforge.eventbus.EventBus.lambda$addListener$11(EventBus.java:201) at net.minecraftforge.eventbus.EventBus.post(EventBus.java:266) at net.minecraftforge.fml.javafmlmod.FMLModContainer.fireEvent(FMLModContainer.java:105) at java.util.function.Consumer.lambda$andThen$0(Unknown Source) at java.util.function.Consumer.lambda$andThen$0(Unknown Source) at net.minecraftforge.fml.ModContainer.transitionState(ModContainer.java:111) at net.minecraftforge.fml.ModList.lambda$null$9(ModList.java:120) at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) at java.util.stream.AbstractPipeline.copyInto(Unknown Source) at java.util.stream.ForEachOps$ForEachTask.compute(Unknown Source) at java.util.concurrent.CountedCompleter.exec(Unknown Source) at java.util.concurrent.ForkJoinTask.doExec(Unknown Source) at java.util.concurrent.ForkJoinTask.doInvoke(Unknown Source) at java.util.concurrent.ForkJoinTask.invoke(Unknown Source) at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source) at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(Unknown Source) at java.util.stream.AbstractPipeline.evaluate(Unknown Source) at java.util.stream.ReferencePipeline.forEach(Unknown Source) at java.util.stream.ReferencePipeline$Head.forEach(Unknown Source) at net.minecraftforge.fml.ModList.lambda$dispatchParallelEvent$10(ModList.java:120) at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(Unknown Source) at java.util.concurrent.ForkJoinTask.doExec(Unknown Source) at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source) at java.util.concurrent.ForkJoinPool.runWorker(Unknown Source) at java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source) Caused by: java.lang.NoSuchMethodError: fr.fifoube.packets.PacketMoneyData.handle(Lfr/fifoube/packets/PacketMoneyData;Ljava/util/function/Supplier;)V at java.lang.invoke.MethodHandleNatives.resolve(Native Method) at java.lang.invoke.MemberName$Factory.resolve(Unknown Source) at java.lang.invoke.MemberName$Factory.resolveOrFail(Unknown Source) at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(Unknown Source) at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(Unknown Source) at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source) ... 29 more [23:25:32.445] [modloading-worker-1/ERROR] [ne.mi.fm.ja.FMLModContainer/LOADING]: Caught exception during event FMLCommonSetupEvent dispatch for modid economyinc java.lang.BootstrapMethodError: java.lang.NoSuchMethodError: fr.fifoube.packets.PacketMoneyData.handle(Lfr/fifoube/packets/PacketMoneyData;Ljava/util/function/Supplier;)V at fr.fifoube.packets.PacketsRegistery.registerNetworkPackets(PacketsRegistery.java:18) ~[main/:?] at fr.fifoube.main.ModEconomyInc.setup(ModEconomyInc.java:74) ~[main/:?] at net.minecraftforge.eventbus.EventBus.doCastFilter(EventBus.java:209) ~[eventbus-0.8.1-service.jar:?] at net.minecraftforge.eventbus.EventBus.lambda$addListener$11(EventBus.java:201) ~[eventbus-0.8.1-service.jar:?] at net.minecraftforge.eventbus.EventBus.post(EventBus.java:266) ~[eventbus-0.8.1-service.jar:?] at net.minecraftforge.fml.javafmlmod.FMLModContainer.fireEvent(FMLModContainer.java:105) ~[?:25.0] at java.util.function.Consumer.lambda$andThen$0(Unknown Source) ~[?:1.8.0_201] at java.util.function.Consumer.lambda$andThen$0(Unknown Source) ~[?:1.8.0_201] at net.minecraftforge.fml.ModContainer.transitionState(ModContainer.java:111) ~[?:?] at net.minecraftforge.fml.ModList.lambda$null$9(ModList.java:120) ~[?:?] at java.util.stream.ForEachOps$ForEachOp$OfRef.accept(Unknown Source) ~[?:1.8.0_201] at java.util.ArrayList$ArrayListSpliterator.forEachRemaining(Unknown Source) ~[?:1.8.0_201] at java.util.stream.AbstractPipeline.copyInto(Unknown Source) ~[?:1.8.0_201] at java.util.stream.ForEachOps$ForEachTask.compute(Unknown Source) ~[?:1.8.0_201] at java.util.concurrent.CountedCompleter.exec(Unknown Source) ~[?:1.8.0_201] at java.util.concurrent.ForkJoinTask.doExec(Unknown Source) ~[?:1.8.0_201] at java.util.concurrent.ForkJoinTask.doInvoke(Unknown Source) ~[?:1.8.0_201] at java.util.concurrent.ForkJoinTask.invoke(Unknown Source) ~[?:1.8.0_201] at java.util.stream.ForEachOps$ForEachOp.evaluateParallel(Unknown Source) ~[?:1.8.0_201] at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateParallel(Unknown Source) ~[?:1.8.0_201] at java.util.stream.AbstractPipeline.evaluate(Unknown Source) ~[?:1.8.0_201] at java.util.stream.ReferencePipeline.forEach(Unknown Source) ~[?:1.8.0_201] at java.util.stream.ReferencePipeline$Head.forEach(Unknown Source) ~[?:1.8.0_201] at net.minecraftforge.fml.ModList.lambda$dispatchParallelEvent$10(ModList.java:120) ~[?:?] at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(Unknown Source) [?:1.8.0_201] at java.util.concurrent.ForkJoinTask.doExec(Unknown Source) [?:1.8.0_201] at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(Unknown Source) [?:1.8.0_201] at java.util.concurrent.ForkJoinPool.runWorker(Unknown Source) [?:1.8.0_201] at java.util.concurrent.ForkJoinWorkerThread.run(Unknown Source) [?:1.8.0_201] Caused by: java.lang.NoSuchMethodError: fr.fifoube.packets.PacketMoneyData.handle(Lfr/fifoube/packets/PacketMoneyData;Ljava/util/function/Supplier;)V at java.lang.invoke.MethodHandleNatives.resolve(Native Method) ~[?:1.8.0_201] at java.lang.invoke.MemberName$Factory.resolve(Unknown Source) ~[?:1.8.0_201] at java.lang.invoke.MemberName$Factory.resolveOrFail(Unknown Source) ~[?:1.8.0_201] at java.lang.invoke.MethodHandles$Lookup.resolveOrFail(Unknown Source) ~[?:1.8.0_201] at java.lang.invoke.MethodHandles$Lookup.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_201] at java.lang.invoke.MethodHandleNatives.linkMethodHandleConstant(Unknown Source) ~[?:1.8.0_201] ... 29 more [23:25:32.492] [modloading-worker-2/DEBUG] [ne.mi.fm.ja.FMLModContainer/LOADING]: Fired event for modid forge : FMLCommonSetupEvent [23:25:32.494] [Server thread/FATAL] [ne.mi.fm.ModLoader/]: Failed to complete lifecycle event SETUP, 1 errors found [23:25:32.494] [Server thread/ERROR] [minecraft/MinecraftServer]: Encountered an unexpected exception net.minecraftforge.fml.LoadingFailedException: null at net.minecraftforge.fml.ModLoader.dispatchAndHandleError(ModLoader.java:157) ~[?:?] at net.minecraftforge.fml.ModLoader.loadMods(ModLoader.java:144) ~[?:?] at net.minecraftforge.fml.server.ServerModLoader.begin(ServerModLoader.java:44) ~[?:?] at net.minecraft.server.dedicated.DedicatedServer.init(DedicatedServer.java:119) ~[?:?] at net.minecraft.server.MinecraftServer.run(MinecraftServer.java:595) [?:?] at java.lang.Thread.run(Unknown Source) [?:1.8.0_201] [23:25:32.504] [Forge Version Check/INFO] [ne.mi.fm.VersionChecker/]: [forge] Starting version check at https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json [23:25:32.522] [Server thread/ERROR] [minecraft/MinecraftServer]: This crash report has been saved to: D:\Documents_D\EconomyInc\MC1132\EconomyIncV16MC1132v250193\run\.\crash-reports\crash-2019-05-10_23.25.32-server.txt [23:25:32.546] [Server thread/INFO] [minecraft/MinecraftServer]: Stopping server [23:25:32.547] [Server thread/INFO] [minecraft/MinecraftServer]: Saving worlds [23:25:32.550] [Server Shutdown Thread/INFO] [minecraft/MinecraftServer]: Stopping server [23:25:32.551] [Server Shutdown Thread/INFO] [minecraft/MinecraftServer]: Saving worlds
EDIT : Je n’ai rien dis, ca fonctionne! J’avais oublier d’enlever l’autre OnlyIn x)
Merci!
-
Ah visiblement il cherche quand même la méthode run, donc en effet ce n’est bon.
Ceci devrait aussi fonctionner :ctx.get().enqueueWork(() -> { DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> { EntityPlayerSP player = Minecraft.getInstance().player; }); });
-
@robin4002 a dit dans Communiquer entre le client et le serveur : le réseau et les paquets :
Ah visiblement il cherche quand même la méthode run, donc en effet ce n’est bon.
Pas d’autres choix que faire ça alors :DistExecutor.runWhenOn(Dist.CLIENT, () -> () -> { EntityPlayerSP player = Minecraft.getInstance().player; });
J’avais oublier d’enlever l’autre OnlyIn sur le handle, ca fonctionne parfaitement. Maintenant tu dis que ce n’est pas “propre”, tu vois une alternative plus propre ?
-
C’est entièrement valide concernant la logique, donc c’est propre à ce niveau.
C’est par contre plus lourd niveau syntaxe que l’écriture avec une fonction lambda.
-
@robin4002 Ok je vois, donc pas d’autre possibilité de faire alors ? A part ce que tu as mis au dessus ? C’était beaucoup plus propre en 1.12.2
-
Je ne pense pas qu’il y a d’autres moyens.
EDIT en fait si, comme ça :public static void handle(PacketXXXX packet, Supplier<NetworkEvent.Context> ctx) { ctx.get().enqueueWork(() -> handleClient(packet)); ctx.get().setPacketHandled(true); } @OnlyIn(Dist.CLIENT) public static void handleClient(PacketXXXX packet) { EntityPlayerSP player = Minecraft.getInstance().player; }
-
-
-