Files

20 KiB

betterMcCommands

Java Paper Kyori Adventure License

Une bibliothèque Java moderne, fluide et événementielle permettant de créer, structurer et gérer des commandes Minecraft de façon hautement dynamique sans aucune déclaration préalable dans plugin.yml.


📑 Sommaire


📚 Guides Détaillés (Dossier /docs)

Pour aller plus loin, consultez la documentation modulaire :


🌟 Fonctionnalités

  • DSL Fluide & Déclaratif : Déclaration intuitive basée sur le pattern Builder chainable (BetterMcCommands.builder("ma-commande")).
  • Zéro plugin.yml : Injection directe par réflexion dans la CommandMap du serveur Minecraft avec support du dé-enregistrement à chaud.
  • Validation Métier Directe & Extensible :
    • Validateurs au niveau commande (.validate(ctx -> condition, "Erreur..."))
    • Validateurs au niveau argument (argument.validate(val -> condition, "Erreur..."))
  • Confirmation Préalable (Double validation) : Sécurisation des commandes critiques (/disband, /delete-data) avec expiration (.requireConfirmation(Duration.ofSeconds(15))).
  • Répétition & Tâches Cadencées (CommandRepeater) : Répétition automatique d'une action à intervalles réguliers avec suivi de progression et annulation.
  • Système d'Arguments Typés & Auto-complétion :
    • Conversion et validation automatiques avec messages d'erreurs clairs.
    • Auto-complétion dynamique (Tab-Complete) intelligente selon le type de chaque argument.
  • Moteur d'Événements Intégré & Annulable :
    • Interception des commandes au niveau local (sur la commande) ou global via un bus d'événements.
    • Annulation de l'exécution (PreExecuteEvent) pour intégrer facilement des vérifications (combat tag, économie, inventaire).
  • Temps de recharge (Cooldowns) natifs : Cooldowns par joueur avec support des permissions de bypass.
  • Formatage Moderne Kyori Adventure / MiniMessage : Couleurs HEX, dégradés (<gradient>), composants enrichis et fallback automatique.

🏗️ Architecture & Flux d'Exécution

flowchart TD
    Player["Joueur ou Console"] -->|Invoque /demo give Luc 10| Wrapper["PaperCommandWrapper (Bukkit)"]
    Wrapper --> Dispatcher["CommandDispatcher (Moteur de routage)"]
    
    Dispatcher --> STypeCheck{"Vérification Type Émetteur"}
    STypeCheck -- Non conforme --> EvtPerm1["Dispatch PermissionDeniedEvent"]
    
    STypeCheck -- Conforme --> PermCheck{"Vérification Permissions"}
    PermCheck -- Manquante --> EvtPerm2["Dispatch PermissionDeniedEvent"]
    
    PermCheck -- Valide --> CoolCheck{"Vérification Cooldown"}
    CoolCheck -- Actif --> EvtCool["Dispatch CommandCooldownEvent"]
    
    CoolCheck -- Inactif --> ArgParse{"Parsing & Validation des Arguments"}
    ArgParse -- Erreur format / invalide --> EvtSyntax["Dispatch CommandSyntaxErrorEvent"]
    
    ArgParse -- Succès --> ConfCheck{"Confirmation Requise ?"}
    ConfCheck -- Non confirmée --> EvtConf["Dispatch ConfirmationRequiredEvent & Invite à retaper"]
    
    ConfCheck -- Confirmée ou Non requise --> ValCheck{"Validateurs (CommandValidator)"}
    ValCheck -- Invalide --> ValErr["Envoi Message d'Erreur & Arrêt"]
    
    ValCheck -- Valide --> EvtPre{"Dispatch CommandPreExecuteEvent"}
    EvtPre -- Annulé (setCancelled) --> Cancelled["Arrêt de l'exécution"]
    
    EvtPre -- Non annulé --> Exec["Exécution du CommandExecutor métier"]
    Exec --> ApplyCooldown["Application du Cooldown"]
    Exec --> EvtPost["Dispatch CommandPostExecuteEvent (Métriques/Logs)"]

📦 Installation & Configuration

Gradle (Kotlin DSL)

repositories {
    mavenCentral()
    maven("https://repo.papermc.io/repository/maven-public/")
}

dependencies {
    implementation("fr.luc:betterMcCommands:1.0.0-SNAPSHOT")
}

🚀 Guide de Démarrage Rapide

1. Initialisation dans la classe principale (JavaPlugin)

package fr.luc.monplugin;

import fr.luc.bettermccommands.BetterMcCommands;
import org.bukkit.plugin.java.JavaPlugin;

public class MonPlugin extends JavaPlugin {

    private BetterMcCommands commands;

    @Override
    public void onEnable() {
        // 1. Initialisation du gestionnaire pour ce plugin
        this.commands = BetterMcCommands.create(this);

        // 2. Enregistrement d'écouteurs d'événements globaux (optionnel)
        this.commands.registerListeners(new MonEcouteurDeCommandes());

        // 3. Enregistrement de vos commandes
        creerCommandes();
    }

    private void creerCommandes() {
        BetterMcCommands.builder("ping")
                .description("Répond pong !")
                .executes(context -> context.replySuccess("Pong !"))
                .register();
    }

    @Override
    public void onDisable() {
        // Désenregistrement à chaud et nettoyage propre
        if (commands != null) {
            commands.unregisterAll();
        }
    }
}

💡 Exemples de Commandes

1. Commande de Démonstration Complète (/commande-demo)

BetterMcCommands.builder("commande-demo")
    .description("Commande principale de démonstration")
    .aliases("demo", "cdemo")
    .permission("bettermc.demo")

    // Hook local avant exécution (annulable)
    .onPreExecute(event -> {
        if (event.isPlayer() && isInCombat(event.getPlayer())) {
            event.setCancelled(true);
            event.reply("<red>Vous ne pouvez pas exécuter cette commande en combat !</red>");
        }
    })

    // Exécution de la commande racine
    .executes(context -> {
        context.reply("<gradient:#00d2ff:#3a7bd5><bold>=== betterMcCommands ===</bold></gradient>");
        context.reply("<gray>Utilisez <yellow>/demo give</yellow> pour donner des ressources.</gray>");
    })

    // Sous-commande avec arguments typés et valeur par défaut
    .subcommand(BetterMcCommands.subBuilder("give")
        .description("Donne des ressources à un joueur cible")
        .permission("bettermc.demo.give")
        .argument(Arguments.player("cible").description("Joueur recevant les items"))
        .argument(Arguments.integer("quantite", 1, 64).defaultValue(1).description("Nombre d'items (1-64)"))
        .executes(context -> {
            Player target = context.getTargetPlayer("cible");
            int quantite = context.getInt("quantite");

            context.replySuccess("Attribution de <gold>" + quantite + "</gold> items à <aqua>" + target.getName() + "</aqua> !");
        })
    )

    .register();

2. Sous-Commandes Imbriquées

Créez des arbres hiérarchiques profonds de façon fluide :

BetterMcCommands.builder("guilde")
    .description("Gestion de votre guilde")
    .subcommand(BetterMcCommands.subBuilder("admin")
        .permission("guilde.admin")
        .subcommand(BetterMcCommands.subBuilder("rank")
            .subcommand(BetterMcCommands.subBuilder("set")
                .argument(Arguments.player("joueur"))
                .argument(Arguments.enumOf("grade", MonGradeEnum.class))
                .executes(context -> {
                    Player p = context.getTargetPlayer("joueur");
                    MonGradeEnum grade = context.get("grade", MonGradeEnum.class);
                    context.replySuccess("Grade de " + p.getName() + " mis à jour vers " + grade.name());
                })
            )
        )
    )
    .register();

3. Arguments Gourmands (Greedy String)

Permet de capturer tout le reste de la ligne sans nécessiter de guillemets (idéal pour les annonces, motifs de sanctions, broadcasts) :

BetterMcCommands.builder("broadcast")
    .aliases("bc", "annonce")
    .permission("bettermc.broadcast")
    .argument(Arguments.greedyString("message").description("Le texte complet à diffuser"))
    .executes(context -> {
        String message = context.getString("message");
        context.replySuccess("Diffusion : <yellow>" + message + "</yellow>");
    })
    .register();

4. Système de Validation Métier (Pré-requis)

Vous pouvez attacher des validateurs à vos commandes et arguments :

// Validation au niveau de la commande
BetterMcCommands.builder("level-reward")
    .playerOnly()
    .validate(ctx -> ctx.getPlayer().getLevel() >= 10, "Vous devez être niveau 10 minimum !")
    .executes(ctx -> {
        ctx.replySuccess("Récompense de niveau réclamée !");
    })
    .register();

// Validation au niveau d'un argument
BetterMcCommands.builder("setprice")
    .argument(Arguments.decimal("prix").validate(prix -> prix > 0, "Le prix doit être strictement positif !"))
    .executes(ctx -> {
        double prix = ctx.getDouble("prix");
        ctx.replySuccess("Prix configuré à " + prix + " €.");
    })
    .register();

5. Confirmation Préalable & Double Validation (.requireConfirmation)

Sécurisez les commandes destructrices ou sensibles. Le joueur doit ré-exécuter la commande dans le temps imparti pour confirmer l'action :

BetterMcCommands.builder("disband")
    .description("Dissout votre guilde")
    .playerOnly()
    .requireConfirmation(Duration.ofSeconds(15), "<red><bold>ATTENTION</bold> : Cette action est irréversible ! Retapez <yellow>/disband</yellow> dans les 15s pour confirmer.</red>")
    .executes(context -> {
        context.replySuccess("Votre guilde a été dissoute.");
    })
    .register();

6. Répétition & Cadence de Commandes (CommandRepeater)

Permet de répéter une action à intervalle régulier (ex: compte à rebours, diffusion répétée, téléportation avec pulsation) :

BetterMcCommands.builder("countdown")
    .playerOnly()
    .executes(context -> {
        // Répète 5 fois toutes les 1 seconde
        CommandRepeater.repeat(monPlugin, context, 5, Duration.ofSeconds(1),
            progress -> {
                int restants = progress.getTotalRuns() - progress.getCurrentRun() + 1;
                context.reply("<gold>Lancement dans <yellow>" + restants + "</yellow>...</gold>");
            },
            () -> {
                // Action finale
                context.replySuccess("<bold>PARTEZ !</bold>");
            }
        );
    })
    .register();

7. Temps de Recharge (Cooldowns)

BetterMcCommands.builder("kit")
    .description("Récupère le kit quotidien")
    .playerOnly()
    .cooldown(Duration.ofHours(24))
    .cooldownBypass("bettermc.bypass.kit")
    .executes(context -> {
        context.replySuccess("Vous avez reçu votre kit quotidien !");
    })
    .register();

🧩 Catalogue des Arguments Typés

Méthode Factory Type Java de retour Description & Caractéristiques
Arguments.string("nom") String Chaîne de caractères simple (un mot ou délimité).
Arguments.greedyString("message") String Consomme tous les arguments restants jusqu'à la fin de la ligne.
Arguments.integer("nb") Integer Nombre entier sans restriction de bornes.
Arguments.integer("nb", min, max) Integer Nombre entier borné avec validation automatique.
Arguments.decimal("prix") Double Nombre décimal avec support des points (.) et virgules (,).
Arguments.bool("actif") Boolean Booléen (true/false, oui/non, 1/0, on/off).
Arguments.player("cible") org.bukkit.entity.Player Joueur connecté avec auto-complétion des pseudos en ligne.
Arguments.offlinePlayer("joueur") org.bukkit.OfflinePlayer Joueur hors-ligne ou en ligne ciblé par pseudo ou UUID.
Arguments.world("monde") org.bukkit.World Monde Bukkit chargé avec suggestions des mondes existants.
Arguments.location("coords") org.bukkit.Location Coordonnées x,y,z avec support des coordonnées relatives (~).
Arguments.duration("temps") java.time.Duration Durées textuelles (ex: 30s, 15m, 2h, 1d, 7d).
Arguments.enumOf("val", Enum.class) T extends Enum<T> Enum Java avec auto-complétion de toutes les constantes.
Arguments.custom("cle", parseur) T Type personnalisé implémentant ArgumentType<T>.

🎯 Système d'Événements & Cycle de Vie

Liste des Événements Disponibles

Événement Description Annulable ?
CommandPreExecuteEvent Déclenché juste avant l'exécution métier. Permet d'annuler ou modifier l'action. Oui
CommandPostExecuteEvent Déclenché après l'exécution. Contient la durée d'exécution et le statut final. Non
CommandConfirmationRequiredEvent Déclenché lors d'une demande de confirmation préalable pour personnaliser l'invite. Oui
CommandPermissionDeniedEvent Déclenché si la permission est manquante ou si l'émetteur n'est pas autorisé. Oui
CommandSyntaxErrorEvent Déclenché lors d'une erreur de syntaxe ou argument manquant. Oui
CommandCooldownEvent Déclenché lorsqu'un joueur tente d'exécuter une commande sous cooldown actif. Oui
CommandTabCompleteEvent Déclenché lors du calcul des suggestions pour filtrer ou injecter des valeurs. Oui

Écouteurs par Annotations (@CommandEventHandler)

package fr.luc.monplugin.listener;

import fr.luc.bettermccommands.event.*;
import fr.luc.bettermccommands.event.annotation.CommandEventHandler;

public class MonEcouteurDeCommandes {

    // Intercepte toutes les commandes exécutées
    @CommandEventHandler(priority = 5)
    public void onPreExecute(CommandPreExecuteEvent event) {
        System.out.println("[Audit] Commande /" + event.getNode().getFullName() + " par " + event.getSender().getName());
    }

    // Intercepte uniquement la commande 'commande-demo'
    @CommandEventHandler(command = "commande-demo")
    public void onDemoPostExecute(CommandPostExecuteEvent event) {
        if (event.isSuccessful()) {
            System.out.println("[Perf] Exécutée en " + event.getExecutionDuration().toMillis() + "ms.");
        }
    }

    // Personnalise les messages de permission refusée
    @CommandEventHandler
    public void onPermissionDenied(CommandPermissionDeniedEvent event) {
        event.setCustomErrorMessage("<dark_red><bold>Accès Refusé</bold></dark_red> : Vous devez posséder la permission <gray>[" + event.getRequiredPermission() + "]</gray>.");
    }

    // Personnalise les alertes de cooldown
    @CommandEventHandler
    public void onCooldown(CommandCooldownEvent event) {
        event.setCustomMessage("<red>⏳ Patientez <gold>" + event.getRemainingCooldown().toSeconds() + "s</gold> avant de réutiliser cette commande.</red>");
    }
}

📂 Organisation des Packages

fr.luc.bettermccommands/
 ├── BetterMcCommands.java              (Manager principal & Façade d'accès)
 ├── api/                               (Interfaces et contrats publics)
 │    ├── Command.java                  (Commande racine)
 │    ├── CommandNode.java              (Nœud d'arborescence)
 │    ├── CommandContext.java           (Contexte d'exécution et réponses MiniMessage)
 │    ├── CommandExecutor.java          (Action métier exécutable)
 │    ├── CommandSenderType.java        (ALL, PLAYER_ONLY, CONSOLE_ONLY)
 │    ├── CommandResult.java            (SUCCESS, FAILED, CANCELLED, COOLDOWN, etc.)
 │    └── suggestion/                   (Suggestions de complétion)
 ├── builder/                           (DSL Fluent Builder)
 │    ├── AbstractCommandBuilder.java
 │    ├── CommandBuilder.java
 │    └── SubCommandBuilder.java
 ├── argument/                          (Moteur d'arguments typés)
 │    ├── CommandArgument.java
 │    ├── ArgumentType.java
 │    ├── Arguments.java                (Factory d'arguments)
 │    └── type/                         (String, Integer, Player, Duration, Enum, etc.)
 ├── validation/                        (Moteur de validation de commande et d'argument)
 │    ├── CommandValidator.java
 │    ├── ArgumentValidator.java
 │    └── ValidationResult.java
 ├── confirmation/                      (Système de confirmation / Double validation)
 │    ├── ConfirmationManager.java
 │    └── PendingConfirmation.java
 ├── repeat/                            (Tâches répétées & cadence de commandes)
 │    ├── CommandRepeater.java
 │    └── CommandRepeatProgress.java
 ├── event/                             (Bus d'événements & Lifecycle)
 │    ├── CommandEvent.java
 │    ├── CancellableCommandEvent.java
 │    ├── CommandPreExecuteEvent.java
 │    ├── CommandPostExecuteEvent.java
 │    ├── CommandConfirmationRequiredEvent.java
 │    ├── CommandPermissionDeniedEvent.java
 │    ├── CommandSyntaxErrorEvent.java
 │    ├── CommandCooldownEvent.java
 │    ├── CommandTabCompleteEvent.java
 │    ├── CommandEventManager.java
 │    └── annotation/
 │         └── CommandEventHandler.java
 ├── cooldown/                          (Gestionnaire de Cooldowns par joueur)
 │    └── CooldownManager.java
 ├── platform/                          (Ponts plateformes & Injection CommandMap)
 │    ├── CommandDispatcher.java
 │    └── paper/
 │         ├── PaperCommandMapInjector.java
 │         └── PaperCommandWrapper.java
 └── demo/                              (Plugin de démonstration complet)
      ├── DemoPlugin.java
      └── commands/
           ├── DemoBaseCommand.java
           └── DemoListener.java

📄 Licence

Ce projet est sous licence MIT.