Créer la base d'un mod


  • Moddeurs confirmés Rédacteurs Administrateurs

    Une fois l'environnement de développement en place, il est possible de s'attaquer à la création de la base du mod. En raison de la réécriture de nombreux composants de Forge avec l'arrivée de la 1.13, la base d'un mod 1.13.2 est assez différente de celle des versions précédentes. Elle est désormais composée d'un fichier d'information du mod obligatoire puisque c'est le fichier chargé en premier lieu pour détecter la présence du mod (alors que celui-ci était optionnel avant) qui est désormais au format .toml. La deuxième partie est une classe Java (ou autre langage pour lequel il existe un chargeur) qui elle aussi est plutôt différente de ce qui se faisait avant. En clair, pour les anciens moddeurs, il va falloir réapprendre les bases ...

    Sommaire

    Pré-requis

    Le fichier de base du mod

    Dans le dossier de ressources (src/main/resources), un dossier nommé META-INF doit être créé et doit contenir un fichier nommé mods.toml.
    Normalement, le fichier du mod d'exemple est déjà présent, il ne reste qu'à l'adapter.
    Une première ligne, modLoader va indiquer quel chargeur utiliser pour que Forge puisse charger la classe principale. Forge ne sait que charger des mods en Java, l'utilisation d'autres langages nécessite l'installation d'un chargeur approprié. La valeur par défaut est javafml, il est inutile de la changer pour un mod développé en Java. En deuxième temps vient la version du chargeur avec le paramètre loaderVersion. Toujours dans le cas d'un mod Java, inutile de changer la valeur par défaut (25 au moment de la rédaction de ce tutoriel). Ce numéro de version est au format de version maven, [25,) indiquera donc que la version 25 est la minimale et que des versions plus récentes peuvent être utilisées pour charger le mod.
    Enfin issueTrackerURL permet d'indiquer un lien où rapporter les problèmes. Cela a pour but d'aider les utilisateurs à remonter les problèmes rencontrés. On peut y mettre par exemple le lien du traqueur de bogue de Github si le mod a un dépôt Github. Il est également possible de ne rien mettre, cette variable étant optionnelle.

    Juste après il y a la catégorie [[mods]] qui regroupe les propriétés du mod lui-même. Il faut y mettre les éléments suivants (vous trouverez en gras les éléments obligatoires):

    • modId : l'identifiant du mod. Celui-ci doit être unique (deux mods ne peuvent avoir le même modid) et doit être entièrement en minuscule (pas d'espaces non plus, mais on peut y mettre des tirets et des chiffres).
    • version : la version du mod. Par défaut, il y a une variable qui sera remplacée par la valeur se trouvant dans le fichier build.gradle (il est toujours mieux de grouper les constantes au même endroit), mais on peut aussi mettre directement une version ici.
    • displayName : le nom du mod affiché aux utilisateurs. Ici, on est libre de mettre des majuscules, des espaces, etc.
    • updateJSONURL : un lien vers un fichier json contenant les informations de version. Le json en question doit contenir une clé nommée homepage ayant comme valeur une chaîne de caractères étant le lien où télécharger le mod et une deuxième clé nommée promos contenant d'autres clés, une ou deux étant composés de la version de Minecraft suffixée par -latest et / ou par -recommended. La valeur de cette / ces clés sera la dernière version ou la version recommandée de votre mod pour la version de Minecraft en question. Par exemple, si la dernière version d'un mod pour Minecraft 1.12.2 est la version 7.2 et que la dernière version pour la 1.13.2 est la version 8.1, alors le json devra être ainsi :
    {
      "homepage": "https://lien où télécharger ce mod",
      "promos": {
        "1.12.2-latest": "7.2",
        "1.13.2-latest": "8.1",
      }
    }
    

    Par exemple, voilà celui de forge : https://files.minecraftforge.net/maven/net/minecraftforge/forge/promotions_slim.json

    • displayURL : un lien officiel du mod, son site ou une page de présentation sur un forum (un lien vers le projet curseforge par exemple)
    • logoFile : un chemin vers une image étant le logo du mod. Cette image devra être dans l'archive du mod (donc dans le dossier de ressource de l'IDE) il ne peut pas être un lien web.
    • credits : les crédits du mod (on peut y citer une source d'inspiration ou des contributeurs, par exemple).
    • authors : une chaîne de caractères permettant d'y mettre le pseudo de l'auteur ou des auteurs du mod.
    • description : une description de ce que fait le mod. Il est possible de mettre plusieurs lignes de texte, en utilisant la syntaxe suivante :
    description='''
    une longue description ...
    sur plusieurs lignes !
     
    aller, une troisième pour la route :)
    '''
    

    Pour terminer, il est possible de définir des catégories gérant les dépendances. La syntaxe est la suivante :

    [[dependencies.modid]] # il faut penser à remplacer modid ici par l'id du mod
        modId="minecraft" # le mod dont on dépend
        mandatory=true # indique que cette dépendance est obligatoire
        versionRange="[1.13.2]" # version de la dépendance, à nouveau au format maven
        ordering="NONE" # ordre de chargement, on peut mettre BEFORE pour que la dépendance soit chargé avant notre mod, AFTER pour l'inverse.
        side="BOTH" # sur quelle distribution la dépendance doit s'appliquer CLIENT, SERVER ou BOTH pour les deux
    

    Ici, on crée une dépendance à Minecraft en version 1.13.2 uniquement.
    Bien sûr, même sans cette ligne le mod aura de toute façon besoin de Minecraft pour fonctionner, mais le fait d'ajouter une dépendance en précisant la version évitera que le mod soit chargé dans une autre version de Minecraft.
    Même principe pour les dépendances non obligatoires (avec mandatory=false), l'intérêt est de pouvoir préciser la version de la dépendance.

    La classe principale

    Pour la classe principale, il n'y a aucune contrainte de nommage ou d'emplacement. Celle-ci peut être dans n'importe quel package et le nom importe peu (tant que cela respecte la convention Java). Dans les différents tutoriels de Minecraft Forge France, la classe principale sera nommée ModTutorial et le package sera dev.mff.modtutorial.
    Pour cette classe, pas question d'implémenter une méthode main : notre mod n'est pas un programme Java exécutable, mais une bibliothèque pour Forge. Afin d'indiquer que cette classe est la classe principale du mod, il faudra y mettre l'annotation @Mod du package net.minecraftforge.fml.common. Le premier et seul argument est l'identifiant du mod (contrairement aux versions précédentes, désormais tout est chargé depuis le fichier mod.toml, l'annotation @Mod ne contient donc plus grand-chose). Comme l'identifiant du mod sera réutilisé un grand nombre de fois dans le code, il est une bonne pratique de le déclarer en tant que constante (cela limitera les erreurs de frappe lors de sa réutilisation).
    Et voilà à quoi ressemble la classe principale à l'issue de ce premier paragraphe :

    package dev.mff.modtutorial;
    
    import net.minecraftforge.fml.common.Mod;
    
    @Mod(ModTutorial.MOD_ID)
    public class ModTutorial {
    	public static final String MOD_ID = "modtutorial";
    }
    

    Attention

    Il est très important que la valeur que vous avez mis pour modId dans la catégorie [[mods]] de votre mods.toml soit exactement la même que celle que vous mettez dans la constante MOD_ID de votre classe principale.

    Bon, c'est bien beau tout ça, mais pour l'instant il n'y a rien de spécial ici. Il va donc falloir y ajouter un constructeur, dans lequel il faudra enregistrer des fonctions à appeler lors des différents cycles de vie du mod :

    • FMLCommonSetupEvent: Il s'agit du premier cycle de chargement d'un mod. L'essentiel des actions sera à faire à cette étape. Fini le preInit / init / postInit, tout sera soit ici, soit dans des événements spécifiques pour leur tâche (par exemple, chaque registre -- blocs, objets, entités, etc. -- possède son propre événement depuis la 1.12.2, il n'était donc plus utile d'avoir autant d'étapes de chargement).

    • FMLClientSetupEvent: Déclenché après FMLCommonSetupEvent, mais seulement si la distribution du jeu est le client (Minecraft lancé depuis le launcher ou depuis le runClient de l'IDE). Il servira pour des tâches liées au client seulement, comme, par exemple, les entrés clavier, les options graphiques, le rendu, etc.

    • FMLDedicatedServerSetupEvent: Idem, déclenché après FMLCommonSetupEvent mais cette fois seulement si la distribution est un serveur dédié (Minecraft lancé depuis le jar serveur fourni par Mojang). Il servira éventuellement pour des tâches spécifiques au serveur dédié. La plupart des mods n'en aura donc pas besoin, vu qu'en général les ajouts des mods sont plutôt soit spécifiques au client, soit communs aux deux distributions.
      Cet événement sera en revanche fortement utile en cas de création d'un mod avec une base de données (dont toute la logique doit uniquement être gérée sur le serveur dédié, le client ne doit avoir aucun code en rapport avec cette dernière).

    • InterModEnqueueEvent: Déclenché après FMLClientSetupEvent ou FMLDedicatedServerSetupEvent, ce cycle permet d'envoyer des messages à d'autres mods (utile si on souhaite créer une intégration avec un autre mod).

    • InterModProcessEvent: l'opposé de InterModEnqueueEvent, il est déclenché juste après, dans ce cycle il est possible de traiter les messages reçus.

    • FMLFingerprintViolationEvent: Un cycle spécifique déclenché uniquement si la signature du mod ne correspond pas. Son utilisation sera traitée dans un tutoriel dédié à la signature de mod.

    Pour effectuer une action lors d'une phase, il suffit d'écouter les changements de phase avec la fonction suivante :

    FMLJavaModLoadingContext.get().getModEventBus().addListener(this::nomDeLaFonction);
    

    Note

    FMLJavaModLoadingContext devra être changé en fonction du langage utilisé, renseignez-vous auprès du LanguageProvider pour savoir quoi mettre dans le cas d'un autre langage que le Java.

    Ensuite, déclarez la fonction avec en argument la phase voulue (le nom de la fonction importe peu, mais il est mieux de garder quelque chose qui est explicite) :

    	private void nomDeLaFonction(final FMLXXXXEvent event) {
    
    	}
    

    Par exemple :

    	private void setup(final FMLCommonSetupEvent event) {
    		LOGGER.info("Mod tutorial setup");
    	}
    

    Note

    Pour une question de bonne pratique, on préféra l'utilisation d'un logger à la fonctionSystem.out.println : contrairement à cette dernière le logger dispose de plusieurs niveaux, qu'on peut choisir d'afficher ou non. De plus, le logger est nommé, ce qui permet une meilleure séparation des logs. On peut utiliser LogManager.getLogger(); pour obtenir un logger du nom de la classe courante, ou alors déclarer un logger en tant que constante avec comme nom l'identifiant du mod: public static final Logger LOGGER = LogManager.getLogger(MOD_ID);

    Voilà donc à quoi ressemble une classe principale couvrant le cas de la majorité des mods :

    package dev.mff.modtutorial;
    
    import org.apache.logging.log4j.LogManager;
    import org.apache.logging.log4j.Logger;
    
    import net.minecraftforge.fml.common.Mod;
    import net.minecraftforge.fml.event.lifecycle.FMLClientSetupEvent;
    import net.minecraftforge.fml.event.lifecycle.FMLCommonSetupEvent;
    import net.minecraftforge.fml.event.lifecycle.FMLDedicatedServerSetupEvent;
    import net.minecraftforge.fml.javafmlmod.FMLJavaModLoadingContext;
    
    @Mod(ModTutorial.MOD_ID)
    public class ModTutorial {
            // la constante avec l'identifiant du mod
    	public static final String MOD_ID = "modtutorial";
            // le logger du mod
    	public static final Logger LOGGER = LogManager.getLogger(MOD_ID);
    	
            // constructeur de la classe principale, on enregistre ici nos events
    	public ModTutorial() {
    		FMLJavaModLoadingContext.get().getModEventBus().addListener(this::setup);
    		FMLJavaModLoadingContext.get().getModEventBus().addListener(this::clientSetup);
    		FMLJavaModLoadingContext.get().getModEventBus().addListener(this::serverSetup);
    	}
    
    	private void setup(final FMLCommonSetupEvent event) {
    		LOGGER.info("Modtutorial setup");
    	}
    
    	private void clientSetup(final FMLClientSetupEvent event) {
    		LOGGER.info("Mod tutorial client setup");
    	}
    	
    	private void serverSetup(final FMLDedicatedServerSetupEvent event) {
    		LOGGER.info("Mod tutorial server setup");
    	}
    }
    

    La base du mod est maintenant prête !

    Note sur la délégation des distributions

    Cette partie est optionnelle

    Les anciens moddeurs auront surement remarqué que l'annotation SidedProxy n'est plus mentionnée dans ce tutoriel.
    Celle-ci a été remplacée par la classe DistExecutor aillant quasiment le même rôle. Cependant il est désormais possible de s'en passer dans la grande majorité des cas (grâce aux cycles FMLClientSetupEvent et FMLDedicatedServerSetupEvent, mais aussi à l'annotation @EventBusSubscriber qui permet d'enregistrer des événements sur une distribution spécifique (CLIENT ou DEDICATED_SERVER). Nous avons donc décidé de ne plus la mettre dans la base d'un mod, afin d'éviter qu'une majorité de moddeurs débutants se retrouve avec ces classes entièrement vides et sans utilité.

    Pour ceux qui en ont l'utilité, il faudra créer une classe ou une interface qui sera commune aux deux distributions. Ensuite, il faudra deux classes, une par distribution qui héritera de la classe commune ou implémentera l'interface commune.
    Ensuite, dans la classe principale DistExecutor s'utilise comme ceci:

    private ClasseCommune proxy = DistExecutor.runForDist(() -> ClasseClient::new, () -> ClasseServeur::new);
    

    (il peut aussi être public et static, selon les besoins).
    Contrairement à avant où il fallait mettre le chemin et le nom des classes, désormais on met une référence indirecte vers le constructeur de la classe (cela évite que celle-ci soit chargée si la distribution ne correspond pas).
    La variable aura comme valeur une instance de la ClasseClient si le client est lancé, une instance de ClasseServeur si un serveur dédié est lancé.

    Résultat

    Les différents ajouts de code sont retrouvables sur le commit github suivant : https://github.com/MinecraftForgeFrance/mod-tutorial-1.13.2/commit/04db76bfc22671c2e3c7d86752f1ab970a7f1e26

    Licence et attribution

    Creative Commons

    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

    retour Sommaire des tutoriels



  • Il est temps d’arrêté la 1.7.10, enfin.
    Merci pour le tutoriel, une idée s'il y'a la majorité des fonctionnalités de forge implémenter ou il faut mieux attendre pour passez en 1.13.2 voir skip à la 1.14 ?


  • Moddeurs confirmés Rédacteurs Administrateurs

    Il y a déjà suffisamment de chose qui ont été réimplémenté pour que la plupart des mods puissent se mettre à jour.

    En tout cas nous on a décidé de publier un maximum de tutoriel pour la 1.13.2, en tentant de maintenir le rythme d'un tutoriel par semaine.


Log in to reply