Command Framework

Creez des commandes Minecraft puissantes avec une API fluide, des arguments type-safe, de l'auto-completion, des permissions, des cooldowns et une aide generee automatiquement.

Fluent API Type-safe Auto Tab-Complete

Vue d'ensemble

Le Command Framework d'EriAPI fournit une API fluide et intuitive pour creer des commandes Minecraft. Fini les centaines de lignes de code pour gerer le parsing d'arguments, la tab-completion et les permissions manuellement.

Avec EriCommand, tu definis ta commande en enchainant des methodes comme une phrase, et le framework s'occupe de tout le reste : validation des arguments, suggestions de tab-completion, gestion des permissions, cooldowns entre utilisations, et generation automatique de l'aide (/macommande help).

Points forts

  • Fluent API — Enchaine les methodes pour construire ta commande de maniere lisible
  • Arguments type-safe — 7 types d'arguments avec validation automatique (int, float, string, player, item, position, enum)
  • Auto tab-completion — Chaque type d'argument fournit des suggestions intelligentes
  • Permissions — Par niveau OP ou par predicat personnalise
  • Cooldowns — Limitation du rythme d'utilisation par joueur
  • Aide automatique/commande help genere automatiquement a partir de la structure
  • Compatible Brigo — Fonctionne avec Brigo pour les suggestions style Brigadier

Demarrage rapide

Voici comment creer et enregistrer ta premiere commande en moins de 2 minutes.

1. Enregistrer les commandes

Dans ta classe principale @Mod, enregistre tes commandes dans l'evenement FMLServerStartingEvent :

Java — Enregistrement dans @Mod
// Dans votre classe @Mod init :
CommandDemo.register();

// Dans serverStarting :
@Mod.EventHandler
public void onServerStarting(FMLServerStartingEvent event) {
    CommandRegistry.getInstance().onServerStarting(event);
}

2. Creer une commande simple

Cree une classe avec une methode statique register() qui definit ta commande :

Java — Commande "hello"
EriCommand.create("mymod")
    .description("Mon mod")
    .sub("hello")
        .description("Dire bonjour")
        .runs(ctx -> {
            ctx.success("Hello " + ctx.getSender().getName() + " !");
            return 1;
        })
    .end()
    .register();
💡
Resultat en jeu

Tape /mymod hello dans le chat. Tu verras le message "Hello Steve !" (ou ton pseudo) s'afficher en vert.

EriCommand

EriCommand est le point d'entree du framework. C'est un builder fluent qui te permet de definir la structure complete de ta commande : nom, description, sous-commandes, arguments, permissions, cooldowns et executeur.

Methodes du builder

Methode Description
create(name) Cree une nouvelle commande racine avec le nom donne
description(desc) Definit la description affichee dans l'aide
permission(level) Niveau OP requis (0-4)
permission(predicate) Predicat personnalise pour les permissions
rootAliases(...) Alias alternatifs pour la commande racine
autoHelp(boolean) Active/desactive la generation automatique de l'aide (defaut: true)
sub(name) Ouvre un builder de sous-commande
arg(argument) Ajoute un argument type-safe
runs(executor) Definit l'action executee quand la commande est appelee
cooldown(ticks) Cooldown en ticks entre deux utilisations (20 ticks = 1 seconde)
register() Enregistre la commande dans le CommandRegistry

Exemple de base

Java — Builder EriCommand
EriCommand.create("monmod")
    .description("Commandes de MonMod")
    .rootAliases("mm", "mmod")
    .permission(2)           // OP level 2 minimum
    .autoHelp(true)          // /monmod help est genere automatiquement
    .sub("give")
        .description("Donne un item a un joueur")
        .arg(PlayerArg.of("cible"))
        .arg(ItemArg.of("item"))
        .arg(IntArg.of("quantite").range(1, 64).optional(1))
        .runs(ctx -> {
            EntityPlayerMP target = ctx.getPlayer("cible");
            Item item = ctx.getItem("item");
            int amount = ctx.getInt("quantite");
            // ... logique de give
            ctx.success("Donne %dx %s a %s", amount, item.getRegistryName(), target.getName());
            return 1;
        })
    .end()
    .register();

Types d'arguments

Le framework fournit 7 types d'arguments pre-construits, chacun avec sa propre validation et ses suggestions de tab-completion. Tous les arguments suivent le meme pattern : TypeArg.of("nom").

IntArg — Entier

Argument de type entier avec bornes optionnelles et valeur par defaut.

Java — IntArg
// Entier simple
.arg(IntArg.of("amount"))

// Avec bornes
.arg(IntArg.of("amount").range(1, 64))

// Avec valeur par defaut (argument optionnel)
.arg(IntArg.of("amount").range(1, 64).optional(1))

// Recuperation dans l'executeur :
int amount = ctx.getInt("amount");

FloatArg — Decimal

Argument de type decimal (float) avec bornes optionnelles.

Java — FloatArg
// Decimal simple
.arg(FloatArg.of("speed"))

// Avec bornes
.arg(FloatArg.of("speed").range(0.0f, 10.0f))

// Recuperation :
float speed = ctx.getFloat("speed");

StringArg — Chaine de caracteres

Argument texte. Trois modes disponibles : mot simple, chaine entre guillemets, ou chaine "greedy" qui capture tout le reste de la commande.

Java — StringArg
// Mot simple (s'arrete a l'espace)
.arg(StringArg.of("name"))

// Chaine entre guillemets ("mon texte avec espaces")
.arg(StringArg.quoted("text"))

// Greedy — capture tout le reste de la commande
.arg(StringArg.greedy("message"))

// Recuperation :
String name = ctx.getString("name");
⚠️
Attention avec greedy

StringArg.greedy() capture tout le texte restant. Il doit donc etre le dernier argument de la commande. Tout argument place apres sera ignore.

PlayerArg — Joueur en ligne

Argument ciblant un joueur connecte au serveur. La tab-completion propose automatiquement tous les joueurs en ligne.

Java — PlayerArg
.arg(PlayerArg.of("target"))

// Recuperation :
EntityPlayerMP player = ctx.getPlayer("target");

ItemArg — Item du registre

Argument pour un item Minecraft. La tab-completion propose tous les items enregistres dans le registre Forge.

Java — ItemArg
.arg(ItemArg.of("item"))

// Recuperation :
Item item = ctx.getItem("item");

PosArg — Position (x y z)

Argument de position 3D. Consomme 3 tokens (x, y, z). Supporte le prefixe ~ pour les coordonnees relatives a la position du joueur.

Java — PosArg
.arg(PosArg.of("pos"))

// Utilisation en jeu :
// /mymod tp 100 64 -200       (coordonnees absolues)
// /mymod tp ~ ~10 ~            (10 blocs au-dessus du joueur)

// Recuperation :
BlockPos blockPos = ctx.getBlockPos("pos");
Vec3d vec = ctx.getVec3d("pos");

EnumArg — Valeur d'enum

Argument pour une valeur d'enum Java. La tab-completion propose automatiquement toutes les valeurs de l'enum.

Java — EnumArg
public enum GameMode {
    SURVIVAL, CREATIVE, ADVENTURE, SPECTATOR
}

.arg(EnumArg.of("mode", GameMode.class))

// Utilisation en jeu :
// /mymod setmode CREATIVE

// Recuperation :
GameMode mode = ctx.getEnum("mode", GameMode.class);

Resume des types

Type Creation Tab-completion Recuperation
IntArg IntArg.of("n").range(1, 64) Suggestions numeriques ctx.getInt("n")
FloatArg FloatArg.of("f").range(0, 10) Suggestions numeriques ctx.getFloat("f")
StringArg StringArg.of("s") Suggestions personnalisees ctx.getString("s")
PlayerArg PlayerArg.of("p") Joueurs en ligne ctx.getPlayer("p")
ItemArg ItemArg.of("i") Items du registre ctx.getItem("i")
PosArg PosArg.of("pos") ~ ~ ~ (relatif) ctx.getBlockPos("pos")
EnumArg EnumArg.of("e", E.class) Valeurs de l'enum ctx.getEnum("e", E.class)

CommandContext

Le CommandContext est l'objet passe a ton executeur (runs(ctx -> ...)). Il contient toutes les informations sur la commande en cours : l'expediteur, le serveur, et les valeurs des arguments parses.

Acces a l'expediteur

Java — Expediteur
// L'expediteur brut (peut etre la console, un command block, etc.)
ICommandSender sender = ctx.getSender();

// L'expediteur en tant que joueur (lance une erreur si ce n'est pas un joueur)
EntityPlayerMP player = ctx.getSenderAsPlayer();

// Le serveur
MinecraftServer server = ctx.getServer();

Recuperation des arguments

Java — Getters d'arguments
// Types primitifs
int n       = ctx.getInt("amount");
float f     = ctx.getFloat("speed");
String s    = ctx.getString("name");

// Types Minecraft
EntityPlayerMP player = ctx.getPlayer("target");
Item item             = ctx.getItem("item");
BlockPos pos          = ctx.getBlockPos("position");
Vec3d vec             = ctx.getVec3d("position");

// Enum
GameMode mode = ctx.getEnum("mode", GameMode.class);

Methodes de reponse

Le CommandContext fournit des methodes utilitaires pour envoyer des messages formates a l'expediteur. Chaque methode supporte le formatage String.format.

Java — Reponses
// Message neutre (gris)
ctx.reply("Traitement en cours...");

// Message de succes (vert)
ctx.success("Item donne avec succes a %s !", player.getName());

// Message d'erreur (rouge)
ctx.error("Le joueur %s est introuvable.", targetName);

// Message d'information (jaune/dore)
ctx.info("Cooldown restant : %d secondes.", remaining);
Methode Couleur Usage
reply(msg, args...) Gris (defaut) Messages generiques
success(msg, args...) Vert Action reussie
error(msg, args...) Rouge Erreur ou echec
info(msg, args...) Jaune/dore Information, avertissement

Sous-commandes

Les sous-commandes permettent d'organiser ta commande en arborescence. Utilise .sub("nom") pour ouvrir un builder de sous-commande, et .end() pour revenir au niveau parent.

Structure en arbre

Tu peux imbriquer des sous-commandes sur plusieurs niveaux. La methode .end() remonte toujours d'un niveau dans l'arborescence.

Java — Sous-commandes imbriquees
EriCommand.create("admin")
    .description("Commandes d'administration")
    .permission(3)

    // /admin user
    .sub("user")
        .description("Gestion des joueurs")

        // /admin user info <joueur>
        .sub("info")
            .description("Informations sur un joueur")
            .arg(PlayerArg.of("target"))
            .runs(ctx -> {
                EntityPlayerMP p = ctx.getPlayer("target");
                ctx.info("Joueur: %s | Pos: %s", p.getName(), p.getPosition());
                return 1;
            })
        .end()

        // /admin user kick <joueur> [raison]
        .sub("kick")
            .description("Expulser un joueur")
            .arg(PlayerArg.of("target"))
            .arg(StringArg.greedy("raison").optional("Aucune raison"))
            .runs(ctx -> {
                EntityPlayerMP p = ctx.getPlayer("target");
                String reason = ctx.getString("raison");
                p.connection.disconnect(new TextComponentString(reason));
                ctx.success("Joueur %s expulse.", p.getName());
                return 1;
            })
        .end()

    .end() // fin de "user"

    // /admin reload
    .sub("reload")
        .description("Recharger la configuration")
        .runs(ctx -> {
            // ... logique de reload
            ctx.success("Configuration rechargee !");
            return 1;
        })
    .end()

    .register();
💡
Arborescence generee

La commande ci-dessus genere cette structure :

/admin
  ├── user
  │   ├── info <target>
  │   └── kick <target> [raison]
  ├── reload
  └── help

Le sous-commande help est generee automatiquement grace a autoHelp(true) (defaut).

Permissions & Cooldowns

Permissions par niveau OP

La methode .permission(int level) definit le niveau OP minimum requis pour executer la commande. Les niveaux vont de 0 (tous les joueurs) a 4 (proprietaire du serveur).

Java — Niveaux de permission
// Accessible a tous les joueurs
EriCommand.create("info")
    .permission(0)
    ...

// Operateur niveau 2 (commandes de gestion)
EriCommand.create("manage")
    .permission(2)
    ...

// Admin serveur
EriCommand.create("admin")
    .permission(4)
    ...
Niveau Description
0 Tous les joueurs
1 Moderateurs (bypass spawn protection)
2 Game masters (commandes de jeu : /gamemode, /tp...)
3 Administrateurs (/ban, /kick, /op...)
4 Proprietaire serveur (/stop, /save-all...)

Permissions par predicat

Pour des permissions plus complexes (verifier un rang, une team, etc.), utilise un predicat :

Java — Permission par predicat
EriCommand.create("vip")
    .permission(sender -> {
        // Logique personnalisee : verifier un rank, une team, etc.
        if (sender instanceof EntityPlayerMP) {
            EntityPlayerMP player = (EntityPlayerMP) sender;
            return player.getTags().contains("vip");
        }
        return false; // Refuse pour la console / command blocks
    })
    ...

Cooldowns

La methode .cooldown(int ticks) limite la frequence d'utilisation d'une commande par joueur. 20 ticks = 1 seconde. Le CooldownManager gere automatiquement le suivi par UUID.

Java — Cooldowns
EriCommand.create("kit")
    .description("Recevoir un kit")
    .sub("daily")
        .description("Kit quotidien")
        .cooldown(20 * 60 * 60 * 24)  // 24 heures en ticks
        .runs(ctx -> {
            // ... donner le kit
            ctx.success("Kit quotidien recu !");
            return 1;
        })
    .end()
    .sub("starter")
        .description("Kit de depart")
        .cooldown(20 * 60 * 5)  // 5 minutes en ticks
        .runs(ctx -> {
            // ... donner le kit starter
            ctx.success("Kit starter recu !");
            return 1;
        })
    .end()
    .register();
💡
Message de cooldown

Quand un joueur essaie d'utiliser une commande en cooldown, un message automatique lui indique le temps restant avant la prochaine utilisation.

Tab-Completion

Chaque type d'argument fournit automatiquement des suggestions lorsque le joueur appuie sur Tab dans le chat. Aucune configuration supplementaire n'est necessaire.

Suggestions automatiques par type

Type d'argument Suggestions automatiques
IntArg Valeurs numeriques courantes, bornes min/max
FloatArg Valeurs decimales courantes
StringArg Suggestions personnalisees (voir .suggests())
PlayerArg Liste des joueurs en ligne sur le serveur
ItemArg Tous les items du registre Forge (ex: minecraft:diamond)
PosArg ~ ~ ~ (position relative du joueur)
EnumArg Toutes les valeurs de l'enum en majuscules

Suggestions personnalisees

Tu peux ajouter des suggestions manuelles a n'importe quel argument avec .suggests() :

Java — Suggestions personnalisees
// Suggestions statiques
.arg(StringArg.of("color").suggests("red", "green", "blue", "yellow"))

// Les suggestions sont filtrees automatiquement en fonction de ce que
// le joueur a deja tape. Si le joueur tape "re", seul "red" sera propose.

Compatibilite Brigo

Le Command Framework est compatible avec Brigo pour fournir des suggestions de style Brigadier (le systeme de commandes de Minecraft 1.13+). Si Brigo est present, les suggestions seront affichees avec la tooltip au-dessus du champ de saisie.

CommandRegistry

Le CommandRegistry est le singleton central qui gere l'enregistrement de toutes les commandes EriCommand dans Forge. Il est le pont entre le framework et le systeme de commandes Minecraft.

Methodes

Methode Description
getInstance() Retourne l'instance singleton du registre
register(EriCommand) Enregistre une commande dans le registre (appele automatiquement par .register())
onServerStarting(event) Enregistre toutes les commandes dans Forge. A appeler dans FMLServerStartingEvent
size() Retourne le nombre de commandes enregistrees

Integration dans @Mod

Java — Integration complete dans la classe @Mod
@Mod(modid = "mymod", name = "MyMod", version = "1.0")
public class MyMod {

    @Mod.EventHandler
    public void init(FMLInitializationEvent event) {
        // Enregistre toutes tes commandes ici
        MyCommands.register();
        AdminCommands.register();
    }

    @Mod.EventHandler
    public void onServerStarting(FMLServerStartingEvent event) {
        // Enregistre les commandes dans Forge
        CommandRegistry.getInstance().onServerStarting(event);
    }
}
💡
Ordre d'appel

Appelle toujours register() sur tes commandes avant CommandRegistry.getInstance().onServerStarting(event). Le registre ne detecte pas les commandes ajoutees apres l'evenement serveur.

Exemple complet

Voici un exemple complet qui combine toutes les fonctionnalites du Command Framework : sous-commandes, arguments de tous types, permissions, cooldowns et aide automatique.

Java — Commande complete "mygame"
public class GameCommands {

    public enum Difficulty {
        EASY, NORMAL, HARD, EXTREME
    }

    public static void register() {
        EriCommand.create("mygame")
            .description("Commandes du mini-jeu")
            .rootAliases("mg")
            .permission(0)

            // ── /mygame join ──────────────────────────────────
            .sub("join")
                .description("Rejoindre la partie")
                .cooldown(20 * 30) // 30 secondes
                .runs(ctx -> {
                    EntityPlayerMP player = ctx.getSenderAsPlayer();
                    ctx.success("Tu as rejoint la partie !");
                    return 1;
                })
            .end()

            // ── /mygame leave ─────────────────────────────────
            .sub("leave")
                .description("Quitter la partie")
                .runs(ctx -> {
                    EntityPlayerMP player = ctx.getSenderAsPlayer();
                    ctx.info("Tu as quitte la partie.");
                    return 1;
                })
            .end()

            // ── /mygame settings ──────────────────────────────
            .sub("settings")
                .description("Parametres du jeu")
                .permission(2)

                // /mygame settings difficulty <level>
                .sub("difficulty")
                    .description("Definir la difficulte")
                    .arg(EnumArg.of("level", Difficulty.class))
                    .runs(ctx -> {
                        Difficulty diff = ctx.getEnum("level", Difficulty.class);
                        ctx.success("Difficulte definie sur %s.", diff.name());
                        return 1;
                    })
                .end()

                // /mygame settings maxplayers <count>
                .sub("maxplayers")
                    .description("Nombre max de joueurs")
                    .arg(IntArg.of("count").range(2, 32))
                    .runs(ctx -> {
                        int count = ctx.getInt("count");
                        ctx.success("Joueurs max : %d", count);
                        return 1;
                    })
                .end()

                // /mygame settings speed <value>
                .sub("speed")
                    .description("Vitesse du jeu")
                    .arg(FloatArg.of("value").range(0.5f, 5.0f))
                    .runs(ctx -> {
                        float speed = ctx.getFloat("value");
                        ctx.success("Vitesse : %.1f", speed);
                        return 1;
                    })
                .end()

            .end() // fin de "settings"

            // ── /mygame tp <target> <pos> ─────────────────────
            .sub("tp")
                .description("Teleporter un joueur")
                .permission(2)
                .arg(PlayerArg.of("target"))
                .arg(PosArg.of("destination"))
                .runs(ctx -> {
                    EntityPlayerMP target = ctx.getPlayer("target");
                    BlockPos pos = ctx.getBlockPos("destination");
                    target.setPositionAndUpdate(pos.getX(), pos.getY(), pos.getZ());
                    ctx.success("Teleporte %s en %d %d %d.",
                        target.getName(), pos.getX(), pos.getY(), pos.getZ());
                    return 1;
                })
            .end()

            // ── /mygame give <target> <item> [amount] ─────────
            .sub("give")
                .description("Donner un item")
                .permission(2)
                .arg(PlayerArg.of("target"))
                .arg(ItemArg.of("item"))
                .arg(IntArg.of("amount").range(1, 64).optional(1))
                .runs(ctx -> {
                    EntityPlayerMP target = ctx.getPlayer("target");
                    Item item = ctx.getItem("item");
                    int amount = ctx.getInt("amount");
                    target.addItemStackToInventory(new ItemStack(item, amount));
                    ctx.success("Donne %dx %s a %s.",
                        amount, item.getRegistryName(), target.getName());
                    return 1;
                })
            .end()

            // ── /mygame broadcast <message> ──────────────────
            .sub("broadcast")
                .description("Envoyer un message a tous")
                .permission(3)
                .arg(StringArg.greedy("message"))
                .runs(ctx -> {
                    String msg = ctx.getString("message");
                    ctx.getServer().getPlayerList().sendMessage(
                        new TextComponentString("[MyGame] " + msg));
                    ctx.success("Message diffuse !");
                    return 1;
                })
            .end()

            .register();
    }
}

Commandes generees

La structure ci-dessus produit les commandes suivantes :

/mygame join                                    — Rejoindre la partie (cooldown 30s)
/mygame leave                                   — Quitter la partie
/mygame settings difficulty <level>             — Definir la difficulte (OP 2)
/mygame settings maxplayers <count>             — Nombre max de joueurs (OP 2)
/mygame settings speed <value>                  — Vitesse du jeu (OP 2)
/mygame tp <target> <destination>               — Teleporter un joueur (OP 2)
/mygame give <target> <item> [amount]           — Donner un item (OP 2)
/mygame broadcast <message>                     — Envoyer un message (OP 3)
/mygame help                                    — Aide auto-generee
💡
Alias

Grace a .rootAliases("mg"), toutes ces commandes sont aussi accessibles avec /mg (par exemple /mg join, /mg settings difficulty HARD).