Skip to content

Architecture Client/Server et Gestion Réseau (Paquets)

Ce cinquième module est dédié à la compréhension d'un aspect crucial et souvent redouté du modding Minecraft : la distinction absolue entre le Côté Client et le Côté Serveur, ainsi que l'établissement d'une communication réseau propre via les Paquets (Custom Packets).

Maîtriser cette architecture est indispensable pour concevoir des mods stables, fluides et exempts de crashs en multijoueur.


1. Le Paradoxe des Deux Mondes: Client vs Serveur

Minecraft est intrinsèquement divisé en deux instances logiques distinctes, même lorsque vous jouez en mode Solo (le jeu lance alors de manière transparente un serveur logique local en arrière-plan).

Le Serveur (L'Arbitre Suprême)

Il détient la Logique interne et la Vérité absolue du monde de jeu.

  • Rôle : Il gère la génération des chunks, l'intelligence artificielle des entités, la santé des joueurs, les inventaires, ainsi que la validation des modifications sur les blocs.
  • Caractéristique : Il s'exécute de manière purement algorithmique sans interface graphique. Il n'a aucune notion de pixel, de modèle 3D ou de texture.

Le Client (Le Périphérique d'Affichage)

Il est exclusivement responsable du Rendu visuel et des Interfaces utilisateurs.

  • Rôle : Il intercepte les commandes envoyées par le serveur pour afficher l'environnement en 3D, jouer les bruitages, ouvrir les écrans de menus (Screens/GUIs), projeter des particules et exécuter les animations de textures.
  • Caractéristique : Il ne prend aucune décision décisionnelle majeure. Si le client tente de forcer la destruction d'un bloc sans l'autorisation préalable du serveur, ce dernier écrasera l'information et réaffichera immédiatement le bloc (phénomène de désynchronisation ou "rubber-banding").

Règle d'or du Modding Propre : Le Client croit aveuglément, le Serveur décide souverainement. Si vous modifiez un attribut vital (ex : la vie d'un joueur) uniquement côté Client, le changement visuel ne durera qu'une fraction de seconde avant que le Serveur ne restaure brutalement la véritable valeur.


2. Le Diagnostic: Comment savoir où l'on se trouve ?

Pour orienter votre code ou appliquer des logiques distinctes, vous devez interroger l'environnement. Dans la plupart des composants (comme la classe d'un bloc ou d'un objet), Minecraft vous fournit un paramètre Level (le monde). C'est cet objet qui détient la réponse grâce à la méthode isClientSide().

java
@Override
public InteractionResult use(BlockState state, Level level, BlockPos pos, Player player, InteractionHand hand) {
    if (level.isClientSide()) {
        // Nous sommes sur le CLIENT
        // C'est ici qu'on fait apparaître des particules locales ou qu'on joue un effet visuel
        return InteractionResult.SUCCESS;
    } else {
        // Nous sommes sur le SERVEUR
        // C'est ici que s'applique la vraie logique : modification d'inventaire, dégâts, stockage de données
        return InteractionResult.CONSUME;
    }
}
  • level.isClientSide() == true : Vous agissez sur l'affichage local du joueur. Interdiction formelle de modifier des données permanentes ici.
  • level.isClientSide() == false : Vous agissez sur l'instance Serveur (la seule vraie autorité).

3. Le Voyage des Données: Comment passe-t-on d'un côté à l'autre ?

Par défaut, les deux côtés s'ignorent. Pour créer une passerelle, on utilise le réseau distribué. Voici le schéma logique du cycle de navigation d'une information :

[INSTANCE CLIENT]

  1. Le joueur clique sur un bouton de l'UI (Ex : "Améliorer ma vitesse de minage").
  2. Envoi d'un paquet Réseau Custom (ModMessages.sendToServer).

[INSTANCE SERVEUR]

  1. Le serveur reçoit le paquet, vérifie les ressources (XP/items), et applique la modification.
  2. Envoi du paquet de synchronisation (Nouveau niveau) via ModMessages.sendToPlayer.

[INSTANCE CLIENT]

  1. Le client reçoit la nouvelle valeur et met à jour l'affichage de l'interface.

4. La Communication Réseau : Le Réseau Distribué (Network)

Pour déclarer notre canal de communication Forge, nous devons initialiser une classe de gestion centralisée des messages.

Fichier : src/main/java/net/pseudo/tutorialmod/networks/ModMessages.java

java
package net.pseudo.tutorialmod.networks;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.level.ServerPlayer;
import net.minecraftforge.network.NetworkRegistry;
import net.minecraftforge.network.PacketDistributor;
import net.minecraftforge.network.NetworkDirection;
import net.minecraftforge.network.simple.SimpleChannel;
import net.pseudo.tutorialmod.TutorialMod;
import net.pseudo.tutorialmod.networks.packet.SyncEnergyPacket;

public class ModMessages {
    private static SimpleChannel INSTANCE;
    private static int packetId = 0;

    private static int id() { 
        return packetId++; 
    }

    public static void register() {
        SimpleChannel net = NetworkRegistry.ChannelBuilder
            .named(new ResourceLocation(TutorialMod.MOD_ID, "messages"))
            .networkProtocolVersion(() -> "1.0")
            .clientAcceptedVersions(s -> true)
            .serverAcceptedVersions(s -> true)
            .simpleChannel();

        INSTANCE = net;

        // Enregistrement du paquet de synchronisation (Serveur -> Client)
        net.messageBuilder(SyncEnergyPacket.class, id(), NetworkDirection.PLAY_TO_CLIENT)
            .decoder(SyncEnergyPacket::new)
            .encoder(SyncEnergyPacket::toBytes)
            .consumerMainThread(SyncEnergyPacket::handle)
            .add();
    }

    public static <MSG> void sendToPlayer(MSG message, ServerPlayer player) {
        INSTANCE.send(PacketDistributor.PLAYER.with(() -> player), message);
    }

    public static <MSG> void sendToServer(MSG message) {
        INSTANCE.sendToServer(message);
    }
}

5. Anatomie d'un Paquet (Packet)

Chaque paquet de données réseau représente une classe devant impérativement matérialiser 4 étapes distinctes pour sérialiser et désérialiser ses flux :

  • Le Constructeur Primaire : Récupère les données d'origine sous forme d'objets ou de types primitifs.
  • L'Encodeur (toBytes) : Traduit les variables en données binaires au sein d'un FriendlyByteBuf pour le transit sur le réseau.
  • Le Décodeur : Reconstruit les variables d'origine à partir du buffer binaire à la réception du flux.
  • Le Gestionnaire (handle) : Injecte et exécute les instructions finales de manière sécurisée sur le thread principal de Minecraft.

Fichier : src/main/java/net/pseudo/tutorialmod/networks/packet/SyncEnergyPacket.java

java
package net.pseudo.tutorialmod.networks.packet;

import net.minecraft.network.FriendlyByteBuf;
import net.minecraftforge.network.NetworkEvent;
import java.util.function.Supplier;

public class SyncEnergyPacket {
    private final int energy;

    public SyncEnergyPacket(int energy) {
        this.energy = energy;
    }

    public SyncEnergyPacket(FriendlyByteBuf buffer) {
        this.energy = buffer.readInt();
    }

    public void toBytes(FriendlyByteBuf buffer) {
        buffer.writeInt(this.energy);
    }

    public boolean handle(Supplier<NetworkEvent.Context> supplier) {
        NetworkEvent.Context context = supplier.get();
        context.enqueueWork(() -> {
            // ICI: Traitement sécurisé côté Client (Direction PLAY_TO_CLIENT)
            // Permet de mettre à jour la valeur stockée localement pour l'affichage de l'UI
        });
        return true;
    }
}

6. Activation et Initialisation Centrale

Pour assurer le chargement effectif de la topologie réseau, l'enregistrement doit s'exécuter impérativement durant la phase d'initialisation commune du cycle de vie du mod (FMLCommonSetupEvent).

Dans le constructeur de votre classe principale TutorialMod.java :

java
public TutorialMod() {
    IEventBus modEventBus = FMLJavaModLoadingContext.get().getModEventBus();
    modEventBus.addListener(this::commonSetup);
}

private void commonSetup(final FMLCommonSetupEvent event) {
    event.enqueueWork(() -> {
        // Enregistrement sécurisé des paquets sur le thread principal commun
        ModMessages.register();
    });
}

⚠️ Rappel Crucial pour la Sécurité du Multijoueur : Ne faites jamais confiance aveuglément aux requêtes provenant du Client. Si un utilisateur clique sur un bouton pour acheter un objet, l'interface doit envoyer un message disant "Je souhaite acheter l'élément X". C'est ensuite au Serveur de vérifier si le joueur possède les prérequis nécessaires, de déduire la monnaie, puis de générer l'objet dans l'inventaire. Un client qui transmettrait directement l'ordre "Donne-moi l'objet X" s'exposerait à de graves failles de duplication et de triche via des clients modifiés.