Créer une commande


  • Rédacteurs

    Sommaire

    Introduction

    Dans ce tutoriel nous allons voir comment créer une commande. Une commande est une chaine de caractères que le joueur tape dans le chat et qui, lors de son envoie, effectue une action.

    Pré-requis

    Aucun pré-requis

    Code

    L'interface ICommandSender :

    Pour commencer je vais vous parler rapidement de l'interface ICommandSender. Cette interface représente tout chose qui peut exécuter une commande, on a donc dans une liste non exhaustive :

    • Les joueurs
    • La console du serveur
    • Les entités en général
    • Les blocs de commande

    Je vous laisse vous intéresser par vous même aux différentes méthodes qui composent cette interface, je voulais simplement vous indiquer à quoi elle correspondait car nous allons beaucoup la croiser dans le reste du tutoriel.

    L'interface ICommand :

    Une commande est une classe implémentant ICommand, nous allons donc voir une par une les méthodes de cette interface et expliquer à quoi elles servent.

    La première est ICommand#getName qui renvoie un String. Cette méthode doit renvoyer le nom de votre commande, c'est à dire le nom que tapera le joueur juste après le slash. Par exemple, avec la commande suivante :

    /give @a minecraft:apple 64
    

    Le nom de la commande et give, on aurait donc dans la classe de notre commande :

    @Override
    public String getName() {
        return "give";
    }
    

    Ensuite ICommand#getUsage(ICommandSender), qui retourne elle aussi un String, la texte renvoyé doit expliquer à la personne qui exécute la commande, comme marche votre commande. C'est le texte qui sera affiché lors de la commande :

    /help <nom de votre commande>
    

    Ce texte est envoyé grâce à un TextComponentTranslation, vous pouvez donc définir une clé qui correspond à un texte dans les fichiers de langue. On utilisera donc souvent les fichiers de langue, ce qui nous donnera :

    @Override
    public String getUsage(ICommandSender sender) {
        return "macommande.help";
    }
    

    On notera que la méthode nous fournit l'instance de la personne qui a exécuté la commande, le texte affiché peut donc différé selon la personne.

    Nous avons aussi ICommand#getAliases qui renvoie une List<string>, cette liste doit contenir tous les alias de votre commande. Un alias est un substitut au nom de votre commande. Un exemple de commande connue qui utilise un alias est la commande faction, qui a pour alias f. Les deux commandes suivantes ont le même effet :

    /faction
    
    /f
    

    On définira donc la méthode de la façon suivante :

    @Override
    public List<String> getAliases() {
        return Lists.newArrayList("f");
    }
    

    Au tour de ICommand#execute(MinecraftServer, ICommandSender, String[]). Cette méthode n'a pas de valeur de retour mais possède différents arguments qui, dans l'ordre d'apparition correspondent : à l'instance du serveur, à la personne qui a exécuté la commande, aux arguments ajoutés après le nom de la fonction. Comme vous l'aurez deviné cette méthode va servir à réellement exécuter l'action de la commande. C'est ici que la commande give ajoute les objets à l'inventaire des entités, que la commande kill tue les entités ciblées, etc ...
    Pour ce qui est des arguments, si on prend la commande suivante :

    /macommande coucou bonjour salut a+
    

    La tableau reçu est le suivant : String[]{"coucou", "bonjour", "salut", "a+"}
    Le nom de la commande n'est PAS un argument, et les arguments sont séparés par un ou plusieurs espaces lorsque l'on tape la commande.

    Un exemple très simple de cette méthode serait le suivant :

    /**
    * Envoie le message "Test" dans la console du serveur
    */
    @Override
    public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
        System.out.println("Test");
    }
    

    Passons maintenant à la méthode ICommand#checkPermission(MinecraftServer, ICommandeSender) qui renvoie un booléen indiquant si, oui ou non, la personne ayant donné un paramètre a le droit d'exécuter la commande. Il y a ici plusieurs façons de procéder, la plus utilisée est de renvoyer la valeur donnée par ICommandSender#canUseCommand qui vérifie si le joueur possède un certain niveau de permission d'opérateur. C'est donc la plus utilisée mais, de mon point de vue, ce n'est pas le meilleure et je vous expliquerai comment procéder à ma manière.

    Donc pour une commande accessible par tout le monde on POURRAIT définir la méthode de la manière suivante :

    @Override
    public boolean checkPermission(MinecraftServer server, ICommandSender sender) {
        return true;
    }
    

    Comme vous l'avez peut-être déjà expérimenté, on peut compléter automatiquement les commandes en pressant la touche tabulation. Lors de l'appuie sur la touche de tabulation, la méthode ICommand#getTabCompletions(MinecraftServer, ICommandSender, String[], @Nullable BlockPos) qui renvoie une List<string>. La liste renvoyée contient les différentes possibilités pour la complétion du l'argument de la commande actuelle. Donc l'ordre, les arguments de la méthode correspondent : à l'instance du serveur, à la personne qui a envoyé la commande, les arguments déjà tapés et la position du bloc pointé si un bloc est pointé. Pour bien comprendre à quoi correspondent "les arguments déjà tapés" je vais donner 2 exemples :

    Si je fait tabulation après avoir tapé :

    /macommande salut coucou bienven
    

    Je recevrai la chose suivante : String[]{"salut", "coucou", "bienven"}

    Par contre, si je fait tabulation après ça :

    /macommande salut coucou
    

    (il y a bien un espace à la fin, je fais donc tabulation quand je suis un train de taper le 3ème argument de la commande)

    J'obtient : String[]{"salut", "coucou", ""}

    Il vous suffira donc de renvoyer les complétions adéquates.

    Je ne parlerai pas de la dernière méthode qui est ICommand#isUsernameIndex(String[], int) qui ne sert qu'à augmenter une statistique. Vous pouvez vous amuser à la remplir correctement ou renvoyez tout simplement false.

    C'est tout pour l'interface ICommand.

    La classe abstraite CommandBase :

    Minecraft nous propose une implémentation de l'interface ICommand sous la forme d'une classe abstraite afin de nous simplifier la vie. Vous faites donc hériter la classe de votre commande de CommandBase et vous n'avez plus que 3 méthodes à redéfinir qui sont :

    • ICommand#getName
    • ICommand#getUsage
    • ICommand#execute
      Nous les avons déjà toutes vues car elles appartiennent à l'interface ICommand. Mais que sont donc devenues les autres méthodes ? L'auto-complétion renvoie une liste vide, on ne complète donc pas les arguments de la commande. La commande ne possède pas non plus d'alias, une liste vide est là aussi renvoyée. La valeur false est renvoyée pour l'index concernant le nom des joueurs. Mais pour ICommand#checkPermission nous avons la chose suivante :
    @Override
    public boolean checkPermission(MinecraftServer server, ICommandSender sender)
    {
        return sender.canUseCommand(this.getRequiredPermissionLevel(), this.getName());
    }
    

    C'est ce dont je vous avais parler plus haut, on voit ici que l'on fait appel à CommandBase#getPermissionLevel que vous je ne vous conseille pas d'utiliser. Je vous indiquerai comment procéder pour faire quelque chose de "propre". Ce qui est intéressant par contre dans le fait d'utiliser CommandBase, c'est que vous avez accès à plein de fonctions qui vous aideront dans le traitement des arguments de la commande. Je ne vais pas tous les lister ou même expliquer à quoi ils servent mais on peut quand même remarquer les suivants :

    • parseInt(String)
    • parseBoolean(String)
    • getPlayer(MinecraftServer, @Nullable EntityPlayerMP, String)
    • etc ...
      C'est vraiment pour ces fonctions utilitaires que la classe CommandBase est intéressante.

    Deux types de commandes : client et serveur

    Il existe deux types de commandes : les commandes clientes et les commandes serveurs. La différences entre les deux est que pour la commande cliente, la méthode ICommand#execute est exécutée côté client, alors que pour les commandes serveurs, cette méthode est exécutée du côté serveur. La façon de les enregistrer aussi est différente, mais nous verrons ça dans la partie "Enregistrer une commande". Pour les commandes clientes il existe l'interface IClientCommand qui est à implémenter AVEC ICommand et qui permet de dire si l'utilisation du slash est obligatoire pour exécuter la commande. La méthode est la suivante : IClientCommand#allowUsageWithoutPrefix(ICommandSender, String) qui renvoie un booléen. Si vous renvoyez true alors on peut omettre le slash devant le nom de la commande. Les paramètres sont les suivants : personne qui a exécuté la commande, message envoyée dans le chat (donc sans le slash).

    Enregistrer une commande

    Pour les commandes serveurs, celles que vous utiliserez donc le plus souvent, il faut passer par l'event FMLServerStartingEvent, dans votre classe principale rajoutez donc :

    @EventHandler
    public void onServerStarting(FMLServerStartingEvent event) {
    
    }
    

    C'est donc ici que vous allez enregistrer vos commandes. Si vous avez par exemple la classe CommandeTest qui implémente ICommand alors vous pouvez l'enregistrer de la manière suivante :

    @EventHandler
    public void onServerStarting(FMLServerStartingEvent event) {
        event.registerServerCommand(new CommandTest());
    }
    

    C'est aussi simple que ça.

    Dans la cas d'une commande cliente, vous pouvez appeler à n'importe quel moment et n'importe où la ligne suivante :

    ClientCommandHandler.instance.registerCommand(new CommandTest());
    

    CommandTest est bien sûr une classe qui implémente ICommand et qui peut implémenter IClientCommand (Optionnel).

    Les exceptions :

    Parlons très rapidement des exceptions qui peuvent être levées pendant l'exécution de la méthode ICommand#execute. On peut voir qu'elle déclare lever des exceptions de type CommandException, je vais donc les lister :

    • CommandNotFoundException : vous ne devriez pas avoir à l'utiliser car ce n'est pas vous qui vérifiez que la commande existe.
    • EntityNotFoundException : levée quand une entitée n'est pas trouvée.
    • InvalidBlockStateException : levée quand l'état du bloc fournit n'est pas valide.
    • NumberInvalidException : levée quand le nombre passé en argument de la commande n'est pas valide.
    • PlayerNotFoundException : levée quand la recherche d'un joueur a échouée.
    • WrongUsageException : vous pouvez lever celle-ci quand les arguments de la commande ne correspondent pas à une syntaxe valide.
      La plupart de ces exceptions sont levées par les fonctions de la classe CommandBase.

    Créons une commande

    Bon, cette fois ci vous allez avoir droit à un vrai exemple, je vais faire une commande qui apparaitre un zombie nommé Steeve à l'endroit indiqué. Je vais pour cela créer une classe qui s'appelle CommandSteeve qui hérite de CommandeBase. J'ai choisi comme nom pour la commande : steeve.

    Nous avons donc déjà :

    public class CommandSteeve extends CommandBase {
        @Override
        public String getName() {
            return "steeve";
        }
    }
    

    Je vais maintenant enfin vous dévoiler la technique que je propose pour vérifier si le joueur a le droit d'exécuter la commande. Pour cela je vous propose d'utiliser la PermissionAPI mise en place par Forge, vous trouverez un tutoriel sur le forum qui traite de son utilisation. On va partir du principe que l'on autorise n'importe quel autre ICommandSender que les joueurs à utiliser la commande, puis si c'est un joueur on vérifie si il a la permission d'utiliser la commande, on obtient donc :

    @Override
    public boolean checkPermission(MinecraftServer server, ICommandSender sender) {
        if (sender instanceof EntityPlayer)
            return PermissionAPI.hasPermission((EntityPlayer) sender, "modid.command.steeve");
        return true;
    }
    

    Et il ne faut donc pas oublier d'enregistrer la permission durant la phase FMLInitializationEvent de votre mod. Ainsi donc ma classe principale je mets :

    Dans la classe principale

    @EventHandler
    public void init(FMLInitializationEvent event) {
        PermissionAPI.registerNode("modid.command.steeve", DefaultPermissionLevel.OP, "Allows players to use the steeve command");
    }
    

    Voilà une bonne chose de faite, c'est de cette façon que vous devriez vérifier si un joueur a la permission d'utiliser une commande.
    Revenons dans la classe de notre commande à présent, entrons dans la fonction execute que j'ai commenté juste pour vous :

    @Override
    public void execute(MinecraftServer server, ICommandSender sender, String[] args) throws CommandException {
        // On vérifie que l'on ait bien les 3 coordonnées
        if (args.length < 3)
            throw new WrongUsageException(this.getUsage(sender));
    
        // On récupère la position d'apparition grâce à la fonction de la classe CommandBase
        BlockPos pos = parseBlockPos(sender, args, 0, true);
    
        // On instancie l'entité
        EntityZombie steeve = new EntityZombie(sender.getEntityWorld());
        steeve.setLocationAndAngles(pos.getX(), pos.getY(), pos.getZ(), 0f, 0f);
        steeve.setNoAI(true);
        steeve.setCustomNameTag("Steeve");
        steeve.setAlwaysRenderNameTag(true);
    
        // On la fait apparaitre
        sender.getEntityWorld().spawnEntity(steeve);
    
        // On avertie le joueur que ça a fonctionné
        sender.sendMessage(new TextComponentString("Steeve est apparue."));
    }
    

    Et on ajoute de l'autocomplétion car cela fait toujours plaisir, c'est agréable de pouvoir utiliser la touche tabulation pour compléter les commandes :

    @Override
    public List<String> getTabCompletions(MinecraftServer server, ICommandSender sender, String[] args, BlockPos target) {
        // Si le bloc visé n'est pas null
        if (target != null) {
            // Suivant la position de l'argument on complète avec la position du
            // bloc visé
            switch (args.length) {
                case 1:
                    return Lists.newArrayList(String.valueOf(target.getX()));
                case 2:
                    return Lists.newArrayList(String.valueOf(target.getY()));
                case 3:
                    return Lists.newArrayList(String.valueOf(target.getZ()));
            }
        }
        return Lists.newArrayList();
    }
    

    La classe de la commande est terminée, il ne nous reste plus qu'à l'enregistrer :

    Dans la classe principale

    @EventHandler
    public void serverStarting(FMLServerStartingEvent event) {
        event.registerServerCommand(new CommandSteeve());
    }
    

    Résultat

    Ça fonctionne, trust me.

    Crédits

    Rédaction : BrokenSwing

    Correction : Folgansky


    Ce tutoriel de BrokenSwing 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

    retourRetour vers le sommaire des tutoriels