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.
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 helpgenere 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 :
// 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 :
EriCommand.create("mymod")
.description("Mon mod")
.sub("hello")
.description("Dire bonjour")
.runs(ctx -> {
ctx.success("Hello " + ctx.getSender().getName() + " !");
return 1;
})
.end()
.register();
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
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.
// 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.
// 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.
// 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");
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.
.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.
.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.
.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.
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
// 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
// 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.
// 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.
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();
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).
// 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 :
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.
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();
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() :
// 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
@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);
}
}
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.
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
Grace a .rootAliases("mg"), toutes ces commandes sont aussi accessibles avec
/mg (par exemple /mg join, /mg settings difficulty HARD).