docs: ajout de la suite de documentation complète dans /docs et mise à jour du README
This commit is contained in:
@@ -10,6 +10,7 @@ Une bibliothèque Java moderne, fluide et événementielle permettant de créer,
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [📚 Guides Détaillés (Dossier `/docs`)](#-guides-détaillés-dossier-docs)
|
||||
- [🌟 Fonctionnalités](#-fonctionnalités)
|
||||
- [🏗️ Architecture & Flux d'Exécution](#️-architecture--flux-dexécution)
|
||||
- [📦 Installation & Configuration](#-installation--configuration)
|
||||
@@ -30,6 +31,19 @@ Une bibliothèque Java moderne, fluide et événementielle permettant de créer,
|
||||
|
||||
---
|
||||
|
||||
## 📚 Guides Détaillés (Dossier `/docs`)
|
||||
|
||||
Pour aller plus loin, consultez la documentation modulaire :
|
||||
* 📘 [**01. Guide de Démarrage Rapide**](docs/01_GETTING_STARTED.md) : Installation, structure de plugin et initialisation.
|
||||
* 🏗️ [**02. Fluent Builder DSL**](docs/02_COMMAND_BUILDER_DSL.md) : Déclaration de commandes racines, sous-commandes imbriquées, restrictions et alias.
|
||||
* 🧩 [**03. Arguments Typés & Auto-complétion**](docs/03_TYPED_ARGUMENTS.md) : Catalogue exhaustif des arguments, parsers personnalisés et Tab-Complete.
|
||||
* 🎯 [**04. Système d'Événements & Cycle de Vie**](docs/04_LIFECYCLE_EVENTS.md) : Hooks d'exécution, écouteurs `@CommandEventHandler` et gestion des erreurs.
|
||||
* 🛡️ [**05. Validation & Double Confirmation**](docs/05_VALIDATION_AND_CONFIRMATION.md) : Validateurs de pré-requis et confirmations interactives.
|
||||
* ⏳ [**06. Cooldowns & Répétition de Commandes**](docs/06_COOLDOWNS_AND_REPEAT.md) : Temps de recharge, bypass et exécution répétée (`CommandRepeater`).
|
||||
* ⚙️ [**07. Plateforme & Injection CommandMap**](docs/07_PLATFORM_AND_INJECTION.md) : Fonctionnement sous le capot, réflexion et hot-reload.
|
||||
|
||||
---
|
||||
|
||||
## 🌟 Fonctionnalités
|
||||
|
||||
- **DSL Fluide & Déclaratif** : Déclaration intuitive basée sur le pattern Builder chainable (`BetterMcCommands.builder("ma-commande")`).
|
||||
|
||||
@@ -0,0 +1,127 @@
|
||||
# 🚀 Guide de Démarrage Rapide - betterMcCommands
|
||||
|
||||
Ce guide vous accompagne dans l'installation, la configuration et la création de vos premières commandes avec la bibliothèque **betterMcCommands**.
|
||||
|
||||
---
|
||||
|
||||
## 📦 1. Installation
|
||||
|
||||
### Gradle (Kotlin DSL)
|
||||
|
||||
Ajoutez les dépôts et la dépendance dans votre fichier `build.gradle.kts` :
|
||||
|
||||
```kotlin
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven("https://repo.papermc.io/repository/maven-public/")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
// Intégration de betterMcCommands
|
||||
implementation("fr.luc:betterMcCommands:1.0.0-SNAPSHOT")
|
||||
}
|
||||
```
|
||||
|
||||
### Gradle (Groovy DSL)
|
||||
|
||||
```groovy
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven { url 'https://repo.papermc.io/repository/maven-public/' }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation 'fr.luc:betterMcCommands:1.0.0-SNAPSHOT'
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ 2. Initialisation dans votre Plugin Bukkit/Paper
|
||||
|
||||
Créez une instance de `BetterMcCommands` dans la méthode `onEnable()` de votre plugin et nettoyez-la proprement dans `onDisable()` :
|
||||
|
||||
```java
|
||||
package fr.luc.monplugin;
|
||||
|
||||
import fr.luc.bettermccommands.BetterMcCommands;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class MonPlugin extends JavaPlugin {
|
||||
|
||||
private BetterMcCommands commandsManager;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
// 1. Initialisation du gestionnaire avec namespace dédié
|
||||
this.commandsManager = BetterMcCommands.create(this);
|
||||
|
||||
// 2. Enregistrement d'écouteurs d'événements de commandes globaux (optionnel)
|
||||
this.commandsManager.registerListeners(new MonEcouteurDeCommandes());
|
||||
|
||||
// 3. Déclaration et enregistrement des commandes
|
||||
enregistrerCommandes();
|
||||
|
||||
getLogger().info("MonPlugin et betterMcCommands sont prêts !");
|
||||
}
|
||||
|
||||
private void enregistrerCommandes() {
|
||||
// Exemple d'une commande simple : /ping
|
||||
BetterMcCommands.builder("ping")
|
||||
.description("Renvoie pong au joueur")
|
||||
.aliases("p")
|
||||
.executes(context -> {
|
||||
context.replySuccess("Pong !");
|
||||
})
|
||||
.register();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
// 4. Désenregistrement à chaud de toutes les commandes injectées
|
||||
if (commandsManager != null) {
|
||||
commandsManager.unregisterAll();
|
||||
}
|
||||
getLogger().info("MonPlugin a désenregistré ses commandes.");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚡ 3. Zéro `plugin.yml`
|
||||
|
||||
Contrairement aux commandes Bukkit traditionnelles, **vous n'avez pas besoin d'ajouter vos commandes dans le fichier `plugin.yml`**.
|
||||
|
||||
`betterMcCommands` injecte directement vos commandes par réflexion dans la `CommandMap` du serveur lors de l'appel à `.register()`, ce qui permet :
|
||||
* D'enregistrer des commandes dynamiquement selon la configuration.
|
||||
* De recharger (hot-reload) ou retirer des commandes sans redémarrer le serveur.
|
||||
* D'éviter les doublons et les conflits de namespaces.
|
||||
|
||||
---
|
||||
|
||||
## 💬 4. Formatage des Messages avec MiniMessage
|
||||
|
||||
Le `CommandContext` intègre nativement **Kyori Adventure & MiniMessage** :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("bienvenue")
|
||||
.executes(context -> {
|
||||
// Dégradé de couleurs hexadécimales et texte gras
|
||||
context.reply("<gradient:#00d2ff:#3a7bd5><bold>Bienvenue sur le serveur !</bold></gradient>");
|
||||
|
||||
// Message avec préfixe vert standard [✔]
|
||||
context.replySuccess("Votre profil a été chargé avec succès.");
|
||||
|
||||
// Message avec préfixe rouge [✖]
|
||||
context.replyError("Une erreur est survenue.");
|
||||
|
||||
// Message d'information [ℹ]
|
||||
context.replyInfo("Tapez /aide pour obtenir de l'assistance.");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Étape suivante** : Découvrez la syntaxe complète du constructeur dans [02_COMMAND_BUILDER_DSL.md](02_COMMAND_BUILDER_DSL.md).
|
||||
@@ -0,0 +1,128 @@
|
||||
# 🏗️ Fluent Builder DSL - betterMcCommands
|
||||
|
||||
La création de commandes et de sous-commandes repose sur un pattern **Builder chainable et intuitif**.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [1. Commande Racine (`BetterMcCommands.builder`)](#1-commande-racine-bettermccommandsbuilder)
|
||||
- [2. Sous-Commandes Imbriquées (`BetterMcCommands.subBuilder`)](#2-sous-commandes-imbriquées-bettermccommandssubbuilder)
|
||||
- [3. Restrictions d'Émetteurs (`CommandSenderType`)](#3-restrictions-démetteurs-commandsendertype)
|
||||
- [4. Permissions et Alias](#4-permissions-et-alias)
|
||||
- [5. Le Contexte d'Exécution (`CommandContext`)](#5-le-contexte-dexécution-commandcontext)
|
||||
|
||||
---
|
||||
|
||||
## 1. Commande Racine (`BetterMcCommands.builder`)
|
||||
|
||||
Une commande racine est créée via `BetterMcCommands.builder(String name)` :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("spawn")
|
||||
.description("Téléporte le joueur au point d'apparition")
|
||||
.aliases("hub", "lobby")
|
||||
.permission("monplugin.command.spawn")
|
||||
.playerOnly()
|
||||
.executes(context -> {
|
||||
Player player = context.getPlayer();
|
||||
player.teleport(player.getWorld().getSpawnLocation());
|
||||
context.replySuccess("Téléportation au spawn effectuée !");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Sous-Commandes Imbriquées (`BetterMcCommands.subBuilder`)
|
||||
|
||||
Vous pouvez ajouter autant de sous-commandes que nécessaire avec `subBuilder(String name)` et les imbriquer sans limite de profondeur :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("guilde")
|
||||
.description("Gestion de votre guilde")
|
||||
.permission("guilde.use")
|
||||
// /guilde (sans sous-commande) affiche l'aide par défaut
|
||||
.executes(context -> {
|
||||
context.reply("<yellow>=== Commandes de Guilde ===</yellow>");
|
||||
context.reply("<gold>/guilde info</gold> <gray>- Informations sur votre guilde</gray>");
|
||||
context.reply("<gold>/guilde admin rank set <joueur> <grade></gold> <gray>- Rangs</gray>");
|
||||
})
|
||||
|
||||
// Sous-commande : /guilde info
|
||||
.subcommand(BetterMcCommands.subBuilder("info")
|
||||
.description("Affiche les infos de la guilde")
|
||||
.executes(context -> {
|
||||
context.replySuccess("Informations de la guilde affichées.");
|
||||
})
|
||||
)
|
||||
|
||||
// Sous-commandes imbriquées : /guilde admin rank set <joueur> <grade>
|
||||
.subcommand(BetterMcCommands.subBuilder("admin")
|
||||
.description("Administration de guilde")
|
||||
.permission("guilde.admin")
|
||||
.subcommand(BetterMcCommands.subBuilder("rank")
|
||||
.description("Gestion des rangs")
|
||||
.subcommand(BetterMcCommands.subBuilder("set")
|
||||
.description("Attribue un rang à un membre")
|
||||
.argument(Arguments.player("joueur"))
|
||||
.argument(Arguments.string("grade"))
|
||||
.executes(context -> {
|
||||
Player cible = context.getTargetPlayer("joueur");
|
||||
String grade = context.getString("grade");
|
||||
context.replySuccess("Grade " + grade + " appliqué à " + cible.getName());
|
||||
})
|
||||
)
|
||||
)
|
||||
)
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Restrictions d'Émetteurs (`CommandSenderType`)
|
||||
|
||||
Contrôlez qui a le droit d'exécuter la commande :
|
||||
|
||||
* `.playerOnly()` : Seuls les joueurs connectés en jeu peuvent exécuter la commande.
|
||||
* `.consoleOnly()` : Seule la console du serveur peut exécuter la commande.
|
||||
* `.senderType(CommandSenderType.ALL)` (défaut) : Tous les émetteurs sont autorisés.
|
||||
|
||||
En cas de non-respect, un message d'erreur est automatiquement retourné et l'événement `CommandPermissionDeniedEvent` est déclenché.
|
||||
|
||||
---
|
||||
|
||||
## 4. Permissions et Alias
|
||||
|
||||
* `.permission("ma.permission")` : Spécifie la permission Bukkit requise.
|
||||
* `.aliases("alias1", "alias2", ...)` : Enregistre un ou plusieurs alias qui redirigent vers cette commande ou sous-commande.
|
||||
|
||||
---
|
||||
|
||||
## 5. Le Contexte d'Exécution (`CommandContext`)
|
||||
|
||||
L'objet `CommandContext` transmis à l'exécuteur `executes(context -> ...)` fournit :
|
||||
|
||||
| Méthode | Description |
|
||||
|---|---|
|
||||
| `context.getSender()` | Récupère l'émetteur brut (`CommandSender`). |
|
||||
| `context.getPlayer()` | Récupère le joueur (`Player`) ou lève une exception si console. |
|
||||
| `context.getConsole()` | Récupère la console (`ConsoleCommandSender`). |
|
||||
| `context.isPlayer()` | Teste si l'émetteur est un joueur. |
|
||||
| `context.getLabel()` | Retourne l'alias ou le nom utilisé pour l'appel. |
|
||||
| `context.getRawArgs()` | Retourne le tableau des arguments bruts passés par le joueur. |
|
||||
| `context.get(name, Class<T>)` | Récupère un argument converti et typé. |
|
||||
| `context.getString(name)` | Récupère un argument de type chaîne. |
|
||||
| `context.getInt(name)` | Récupère un argument entier. |
|
||||
| `context.getDouble(name)` | Récupère un argument décimal. |
|
||||
| `context.getBoolean(name)` | Récupère un argument booléen. |
|
||||
| `context.getTargetPlayer(name)` | Récupère un joueur cible (`Player`). |
|
||||
| `context.getWorld(name)` | Récupère un monde Bukkit (`World`). |
|
||||
| `context.getDuration(name)` | Récupère une durée (`Duration`). |
|
||||
| `context.reply(miniMessage)` | Envoie un message formaté en MiniMessage. |
|
||||
| `context.replySuccess(msg)` | Envoie un message de succès préfixé en vert. |
|
||||
| `context.replyError(msg)` | Envoie un message d'erreur préfixé en rouge. |
|
||||
| `context.setMetadata(key, val)`| Stocke une donnée contextuelle temporaire. |
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Étape suivante** : Explorez tous les types d'arguments disponibles dans [03_TYPED_ARGUMENTS.md](03_TYPED_ARGUMENTS.md).
|
||||
@@ -0,0 +1,155 @@
|
||||
# 🧩 Système d'Arguments Typés & Auto-complétion - betterMcCommands
|
||||
|
||||
La bibliothèque fournit un ensemble complet d'arguments prêts à l'emploi et supporte la création de types d'arguments personnalisés.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [1. Déclaration d'Arguments](#1-déclaration-darguments)
|
||||
- [2. Arguments Optionnels & Valeurs par Défaut](#2-arguments-optionnels--valeurs-par-défaut)
|
||||
- [3. Arguments Gourmands (Greedy String)](#3-arguments-gourmands-greedy-string)
|
||||
- [4. Catalogue des Types d'Arguments](#4-catalogue-des-types-darguments)
|
||||
- [5. Création d'un Type d'Argument Personnalisé](#5-création-dun-type-dargument-personnalisé)
|
||||
|
||||
---
|
||||
|
||||
## 1. Déclaration d'Arguments
|
||||
|
||||
Les arguments sont déclarés via la fabrique statique `Arguments.*` et ajoutés avec `.argument(...)` :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("givemoney")
|
||||
.argument(Arguments.player("cible").description("Le joueur récepteur"))
|
||||
.argument(Arguments.integer("montant", 1, 100000).description("Montant entre 1 et 100 000"))
|
||||
.executes(context -> {
|
||||
Player target = context.getTargetPlayer("cible");
|
||||
int montant = context.getInt("montant");
|
||||
|
||||
context.replySuccess("Transfert de <gold>" + montant + " $</gold> à <aqua>" + target.getName() + "</aqua>.");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Arguments Optionnels & Valeurs par Défaut
|
||||
|
||||
Vous pouvez marquer n'importe quel argument comme optionnel et lui attribuer une valeur par défaut :
|
||||
|
||||
```java
|
||||
// Valeur par défaut constante (ex: 1)
|
||||
.argument(Arguments.integer("quantite", 1, 64).defaultValue(1))
|
||||
|
||||
// Valeur par défaut dynamique via Supplier
|
||||
.argument(Arguments.world("monde").defaultValue(() -> Bukkit.getWorlds().get(0)))
|
||||
|
||||
// Argument optionnel sans valeur par défaut (retourne null si omis)
|
||||
.argument(Arguments.string("motif").optional())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Arguments Gourmands (Greedy String)
|
||||
|
||||
Un argument marqué comme **greedy** consomme tous les mots restants jusqu'à la fin de la ligne de commande, éliminant ainsi le besoin de guillemets pour les phrases longues :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("broadcast")
|
||||
.argument(Arguments.greedyString("message"))
|
||||
.executes(context -> {
|
||||
String annonce = context.getString("message");
|
||||
Bukkit.broadcastMessage("§6[Annonce] §f" + annonce);
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Catalogue des Types d'Arguments
|
||||
|
||||
### Primitifs & Standards
|
||||
* `Arguments.word("nom")` : Mot simple délimité par un espace.
|
||||
* `Arguments.string("texte")` : Chaîne de caractères standard.
|
||||
* `Arguments.greedyString("message")` : Chaîne gourmande jusqu'à la fin de la ligne.
|
||||
* `Arguments.integer("nb")` : Entier non borné.
|
||||
* `Arguments.integer("nb", min)` : Entier avec un seuil minimal.
|
||||
* `Arguments.integer("nb", min, max)` : Entier borné dans un intervalle fermé `[min, max]`.
|
||||
* `Arguments.decimal("montant")` : Nombre décimal (supporte les points `.` et virgules `,`).
|
||||
* `Arguments.decimal("montant", min, max)` : Nombre décimal borné.
|
||||
* `Arguments.bool("actif")` : Booléen (`true`/`false`, `oui`/`non`, `1`/`0`, `on`/`off`, `yes`/`no`).
|
||||
* `Arguments.enumOf("grade", MonEnum.class)` : Enum Java avec suggestions dynamiques de toutes ses constantes.
|
||||
|
||||
### Types Minecraft & Utilitaires
|
||||
* `Arguments.player("joueur")` : Joueur connecté avec suggestions en direct de tous les joueurs en ligne.
|
||||
* `Arguments.offlinePlayer("joueur")` : Joueur hors-ligne ou en ligne, résolu par pseudo ou UUID.
|
||||
* `Arguments.world("monde")` : Monde Bukkit avec auto-complétion des mondes chargés.
|
||||
* `Arguments.location("coords")` : Coordonnées sous la forme `x,y,z` ou `x,y,z,monde` avec support des coordonnées relatives `~`.
|
||||
* `Arguments.duration("duree")` : Durée temporelle (ex: `30s`, `15m`, `2h`, `1d`, `7d`, `1m 40s`).
|
||||
|
||||
---
|
||||
|
||||
## 5. Création d'un Type d'Argument Personnalisé
|
||||
|
||||
Pour créer un type de donnée métier (ex: une Guilde, un Kit, une Région) :
|
||||
|
||||
```java
|
||||
package fr.luc.monplugin.argument;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
import fr.luc.bettermccommands.api.suggestion.Suggestion;
|
||||
import fr.luc.bettermccommands.argument.ArgumentType;
|
||||
import fr.luc.bettermccommands.argument.CommandArgumentParseException;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class GuildArgumentType implements ArgumentType<Guild> {
|
||||
|
||||
private final GuildManager guildManager;
|
||||
|
||||
public GuildArgumentType(GuildManager guildManager) {
|
||||
this.guildManager = guildManager;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Guild parse(String argumentName, String input, CommandContext context) throws CommandArgumentParseException {
|
||||
Guild guild = guildManager.getGuildByName(input);
|
||||
if (guild == null) {
|
||||
throw new CommandArgumentParseException(argumentName, input, getTypeName(), "La guilde '" + input + "' n'existe pas.");
|
||||
}
|
||||
return guild;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Suggestion> suggest(CommandContext context, String currentInput) {
|
||||
String lower = currentInput == null ? "" : currentInput.toLowerCase();
|
||||
return guildManager.getAllGuilds().stream()
|
||||
.map(Guild::getName)
|
||||
.filter(name -> name.toLowerCase().startsWith(lower))
|
||||
.map(Suggestion::of)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getTypeName() {
|
||||
return "Guilde";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Utilisation dans une commande :
|
||||
```java
|
||||
BetterMcCommands.builder("guilde")
|
||||
.subcommand(BetterMcCommands.subBuilder("join")
|
||||
.argument(Arguments.custom("guilde", new GuildArgumentType(guildManager)))
|
||||
.executes(context -> {
|
||||
Guild guild = context.get("guilde", Guild.class);
|
||||
context.replySuccess("Vous avez rejoint la guilde " + guild.getName());
|
||||
})
|
||||
)
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Étape suivante** : Apprenez à intercepter les événements dans [04_LIFECYCLE_EVENTS.md](04_LIFECYCLE_EVENTS.md).
|
||||
@@ -0,0 +1,120 @@
|
||||
# 🎯 Système d'Événements & Cycle de Vie - betterMcCommands
|
||||
|
||||
`betterMcCommands` intègre un système d'événements complet permettant d'intercepter, modifier, auditer ou bloquer chaque étape de l'exécution d'une commande.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [1. Vue d'Ensemble du Cycle de Vie](#1-vue-densemble-du-cycle-de-vie)
|
||||
- [2. Les Deux Méthodes d'Écoute](#2-les-deux-méthodes-découte)
|
||||
- [Méthode A : Hooks Fluides sur le Builder](#méthode-a--hooks-fluides-sur-le-builder)
|
||||
- [Méthode B : Classes d'Écouteurs avec `@CommandEventHandler`](#méthode-b--classes-découteurs-avec-commandeventhandler)
|
||||
- [3. Référence Exhaustive des Événements](#3-référence-exhaustive-des-événements)
|
||||
- [4. Exemples Pratiques](#4-exemples-pratiques)
|
||||
|
||||
---
|
||||
|
||||
## 1. Vue d'Ensemble du Cycle de Vie
|
||||
|
||||
Lorsqu'un joueur ou la console exécute une commande :
|
||||
|
||||
1. **Vérification d'émetteur et permission** -> `CommandPermissionDeniedEvent` (si refus).
|
||||
2. **Vérification du Cooldown** -> `CommandCooldownEvent` (si cooldown actif).
|
||||
3. **Parsing des arguments** -> `CommandSyntaxErrorEvent` (si syntaxe invalide).
|
||||
4. **Demande de Confirmation** -> `CommandConfirmationRequiredEvent` (si confirmation requise).
|
||||
5. **Pré-exécution** -> `CommandPreExecuteEvent` (**annulable**).
|
||||
6. **Exécution métier** -> Appel du `CommandExecutor`.
|
||||
7. **Post-exécution** -> `CommandPostExecuteEvent` (durée d'exécution, statut de réussite/échec, logs).
|
||||
|
||||
---
|
||||
|
||||
## 2. Les Deux Méthodes d'Écoute
|
||||
|
||||
### Méthode A : Hooks Fluides sur le Builder
|
||||
|
||||
Permet d'attacher un comportement spécifique directement à une commande :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("trade")
|
||||
.onPreExecute(event -> {
|
||||
if (event.isPlayer() && isInCombat(event.getPlayer())) {
|
||||
event.setCancelled(true);
|
||||
event.reply("<red>Vous ne pouvez pas échanger en combat !</red>");
|
||||
}
|
||||
})
|
||||
.onPostExecute(event -> {
|
||||
System.out.println("Trade exécuté en " + event.getExecutionDuration().toMillis() + "ms.");
|
||||
})
|
||||
.executes(context -> {
|
||||
context.replySuccess("Échange ouvert !");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Méthode B : Classes d'Écouteurs avec `@CommandEventHandler`
|
||||
|
||||
Idéal pour centraliser la sécurité, l'audit et les métriques globales :
|
||||
|
||||
```java
|
||||
package fr.luc.monplugin.listener;
|
||||
|
||||
import fr.luc.bettermccommands.event.*;
|
||||
import fr.luc.bettermccommands.event.annotation.CommandEventHandler;
|
||||
|
||||
public class CommandAuditListener {
|
||||
|
||||
// Écoute TOUTES les commandes avant leur exécution
|
||||
@CommandEventHandler(priority = 10)
|
||||
public void onAnyPreExecute(CommandPreExecuteEvent event) {
|
||||
System.out.println("[Audit] Demande d'exécution : /" + event.getNode().getFullName() +
|
||||
" par " + event.getSender().getName());
|
||||
}
|
||||
|
||||
// Écoute UNIQUEMENT la commande racine 'admin'
|
||||
@CommandEventHandler(command = "admin")
|
||||
public void onAdminPostExecute(CommandPostExecuteEvent event) {
|
||||
if (!event.isSuccessful()) {
|
||||
System.err.println("[Alerte Sécurité] Échec sur commande admin : " + event.getNode().getFullName());
|
||||
}
|
||||
}
|
||||
|
||||
// Personnalise les messages de refus de permission
|
||||
@CommandEventHandler
|
||||
public void onPermissionDenied(CommandPermissionDeniedEvent event) {
|
||||
if (event.getRequiredPermission() != null) {
|
||||
event.setCustomErrorMessage("<dark_red><bold>Accès Refusé</bold></dark_red> : Permission requise <gray>[" + event.getRequiredPermission() + "]</gray>.");
|
||||
}
|
||||
}
|
||||
|
||||
// Personnalise l'avertissement 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>");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Pour enregistrer cette classe :
|
||||
```java
|
||||
commandsManager.registerListeners(new CommandAuditListener());
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Référence Exhaustive des Événements
|
||||
|
||||
| Classe d'Événement | Description | Données Disponibles | Annulable ? |
|
||||
|---|---|---|:---:|
|
||||
| `CommandPreExecuteEvent` | Déclenché immédiatement avant l'exécution métier. | `getNode()`, `getSender()`, `getContext()` | ✅ **Oui** |
|
||||
| `CommandPostExecuteEvent` | Déclenché après la fin de l'exécution. | `getResult()`, `getExecutionDuration()`, `getException()`, `isSuccessful()` | ❌ Non |
|
||||
| `CommandPermissionDeniedEvent` | Déclenché lors d'un échec de permission ou restriction console/joueur. | `getRequiredPermission()`, `getRequiredSenderType()`, `setCustomErrorMessage(...)` | ✅ **Oui** |
|
||||
| `CommandSyntaxErrorEvent` | Déclenché lors d'un argument manquant ou format incorrect. | `getErrorReason()`, `getUsage()`, `setCustomMessage(...)` | ✅ **Oui** |
|
||||
| `CommandCooldownEvent` | Déclenché lorsqu'un joueur tente d'exécuter une commande sous cooldown. | `getRemainingCooldown()`, `setCustomMessage(...)` | ✅ **Oui** |
|
||||
| `CommandConfirmationRequiredEvent` | Déclenché lorsqu'une commande requiert une confirmation préalable. | `getTimeout()`, `setCustomPromptMessage(...)` | ✅ **Oui** |
|
||||
| `CommandTabCompleteEvent` | Déclenché lors du calcul des suggestions d'auto-complétion. | `getSuggestions()`, `addSuggestion(...)`, `getCurrentInput()` | ✅ **Oui** |
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Étape suivante** : Découvrez la validation et la confirmation dans [05_VALIDATION_AND_CONFIRMATION.md](05_VALIDATION_AND_CONFIRMATION.md).
|
||||
@@ -0,0 +1,103 @@
|
||||
# 🛡️ Validation & Double Confirmation - betterMcCommands
|
||||
|
||||
La bibliothèque intègre des mécanismes pour valider les pré-requis métier et sécuriser les actions destructrices ou sensibles.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [1. Validation Métier au Niveau de la Commande](#1-validation-métier-au-niveau-de-la-commande)
|
||||
- [2. Validation au Niveau d'un Argument](#2-validation-au-niveau-dun-argument)
|
||||
- [3. Confirmation Préalable & Double Validation (`.requireConfirmation`)](#3-confirmation-préalable--double-validation-requireconfirmation)
|
||||
|
||||
---
|
||||
|
||||
## 1. Validation Métier au Niveau de la Commande
|
||||
|
||||
La méthode `.validate(...)` permet d'exécuter des assertions logiques avant que le gestionnaire principal ne soit appelé. Si la validation échoue, le message d'erreur est automatiquement envoyé et la commande s'interrompt.
|
||||
|
||||
### Exemple : Validation simple par prédicat
|
||||
```java
|
||||
BetterMcCommands.builder("level-reward")
|
||||
.playerOnly()
|
||||
// Teste si le joueur a le niveau requis
|
||||
.validate(ctx -> ctx.getPlayer().getLevel() >= 10, "Vous devez posséder le niveau 10 minimum !")
|
||||
.executes(ctx -> {
|
||||
ctx.replySuccess("Récompense de niveau 10 réclamée !");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
### Exemple : Validateur complexe (`CommandValidator`)
|
||||
```java
|
||||
BetterMcCommands.builder("claim")
|
||||
.playerOnly()
|
||||
.validate(ctx -> {
|
||||
Player p = ctx.getPlayer();
|
||||
if (economy.getBalance(p) < 500) {
|
||||
return ValidationResult.invalid("Solde insuffisant : 500 $ requis.");
|
||||
}
|
||||
if (territoryManager.isProtected(p.getLocation())) {
|
||||
return ValidationResult.invalid("Cette zone est déjà protégée !");
|
||||
}
|
||||
return ValidationResult.valid();
|
||||
})
|
||||
.executes(ctx -> {
|
||||
ctx.replySuccess("Territoire revendiqué avec succès !");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Validation au Niveau d'un Argument
|
||||
|
||||
Vous pouvez également restreindre les valeurs acceptées par un argument :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("setprice")
|
||||
.argument(Arguments.decimal("prix")
|
||||
// Vérification personnalisée de la valeur
|
||||
.validate(prix -> prix > 0, "Le prix doit être strictement supérieur à 0 !")
|
||||
.validate(prix -> prix <= 1000000, "Le prix maximal autorisé est de 1 000 000 $.")
|
||||
)
|
||||
.executes(ctx -> {
|
||||
double prix = ctx.getDouble("prix");
|
||||
ctx.replySuccess("Prix configuré : <gold>" + prix + " $</gold>.");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Confirmation Préalable & Double Validation (`.requireConfirmation`)
|
||||
|
||||
Pour les commandes irréversibles (suppression de données, dissolution de guilde, réinitialisation de statistiques), activez la confirmation préalable avec `.requireConfirmation(...)` :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("disband")
|
||||
.description("Dissout votre guilde")
|
||||
.playerOnly()
|
||||
// Délai de 15 secondes pour confirmer avec message d'avertissement
|
||||
.requireConfirmation(Duration.ofSeconds(15), "<red><bold>ATTENTION</bold> : Cette action est irréversible ! Retapez <yellow>/disband</yellow> dans les 15 secondes pour confirmer.</red>")
|
||||
.executes(context -> {
|
||||
Player p = context.getPlayer();
|
||||
guildManager.disbandGuild(p);
|
||||
context.replySuccess("Votre guilde a été dissoute.");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
### Comment fonctionne le cycle de confirmation ?
|
||||
1. **1ère exécution** : L'émetteur tape `/disband`.
|
||||
- La commande est suspendue.
|
||||
- Une demande de confirmation temporaire est enregistrée dans le `ConfirmationManager`.
|
||||
- Le message d'avertissement est envoyé au joueur.
|
||||
- L'événement `CommandConfirmationRequiredEvent` est diffusé.
|
||||
2. **2ème exécution (dans les 15s)** : L'émetteur retape `/disband`.
|
||||
- La confirmation active est détectée et consommée.
|
||||
- Le code du gestionnaire métier `.executes(...)` s'exécute normalement.
|
||||
3. **Expiration** : Si le joueur ne retape pas la commande dans le délai, la confirmation expire automatiquement.
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Étape suivante** : Découvrez les cooldowns et répétitions dans [06_COOLDOWNS_AND_REPEAT.md](06_COOLDOWNS_AND_REPEAT.md).
|
||||
@@ -0,0 +1,114 @@
|
||||
# ⏳ Cooldowns & Répétition de Commandes - betterMcCommands
|
||||
|
||||
Ce guide détaille la gestion native des temps de recharge (cooldowns) par joueur et l'exécution cadencée / répétée d'actions de commandes.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [1. Configuration des Cooldowns](#1-configuration-des-cooldowns)
|
||||
- [2. Permissions de Bypass de Cooldown](#2-permissions-de-bypass-de-cooldown)
|
||||
- [3. Réinitialisation Manuelle de Cooldown](#3-réinitialisation-manuelle-de-cooldown)
|
||||
- [4. Répétition & Tâches Cadencées (`CommandRepeater`)](#4-répétition--tâches-cadencées-commandrepeater)
|
||||
|
||||
---
|
||||
|
||||
## 1. Configuration des Cooldowns
|
||||
|
||||
Chaque commande ou sous-commande peut posséder son propre temps de recharge :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("kit")
|
||||
.description("Kit quotidien")
|
||||
.playerOnly()
|
||||
// Cooldown de 24 heures
|
||||
.cooldown(Duration.ofHours(24))
|
||||
.executes(context -> {
|
||||
context.replySuccess("Kit quotidien reçu !");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
* Si un joueur exécute la commande pendant son cooldown, l'exécution est bloquée et un message l'informe du temps restant (ex: `Veuillez patienter 14h 25m avant de réutiliser cette commande`).
|
||||
* L'événement `CommandCooldownEvent` est déclenché, permettant d'adapter le message ou d'annuler le blocage sous certaines conditions.
|
||||
|
||||
---
|
||||
|
||||
## 2. Permissions de Bypass de Cooldown
|
||||
|
||||
Pour autoriser certains groupes (VIP, Modérateurs, Staff) à ignorer le temps de recharge :
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("heal")
|
||||
.playerOnly()
|
||||
.cooldown(Duration.ofMinutes(10))
|
||||
// Les joueurs possédant cette permission n'ont aucun cooldown
|
||||
.cooldownBypass("monplugin.heal.bypass")
|
||||
.executes(context -> {
|
||||
Player p = context.getPlayer();
|
||||
p.setHealth(p.getMaxHealth());
|
||||
context.replySuccess("Vous avez été soigné !");
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Réinitialisation Manuelle de Cooldown
|
||||
|
||||
Vous pouvez réinitialiser le cooldown d'un joueur par programmation :
|
||||
|
||||
```java
|
||||
Command healCommand = ...;
|
||||
Player target = Bukkit.getPlayer("Luc");
|
||||
|
||||
commandsManager.getCooldownManager().resetCooldown(healCommand, target);
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. Répétition & Tâches Cadencées (`CommandRepeater`)
|
||||
|
||||
L'utilitaire `CommandRepeater` permet d'exécuter une action liée à la commande de manière répétée à intervalle régulier, tout en gérant le suivi de progression et la possibilité d'annulation prématurée.
|
||||
|
||||
### Exemple : Compte à rebours avant téléportation
|
||||
```java
|
||||
BetterMcCommands.builder("tpcountdown")
|
||||
.playerOnly()
|
||||
.executes(context -> {
|
||||
Player p = context.getPlayer();
|
||||
Location destination = p.getWorld().getSpawnLocation();
|
||||
|
||||
// Répète 5 fois toutes les 1 seconde (20 ticks)
|
||||
CommandRepeater.repeat(monPlugin, context, 5, Duration.ofSeconds(1),
|
||||
progress -> {
|
||||
int secondesRestantes = progress.getTotalRuns() - progress.getCurrentRun() + 1;
|
||||
|
||||
// Si le joueur bouge, on peut annuler la boucle
|
||||
if (hasMoved(p)) {
|
||||
progress.cancel();
|
||||
context.replyError("Téléportation annulée car vous avez bougé !");
|
||||
return;
|
||||
}
|
||||
|
||||
context.reply("<gold>Téléportation dans <yellow>" + secondesRestantes + "s</yellow>...</gold>");
|
||||
},
|
||||
() -> {
|
||||
// Action finale exécutée après la fin des 5 secondes
|
||||
p.teleport(destination);
|
||||
context.replySuccess("Téléporté avec succès !");
|
||||
}
|
||||
);
|
||||
})
|
||||
.register();
|
||||
```
|
||||
|
||||
### Méthodes de `CommandRepeatProgress` :
|
||||
* `progress.getCurrentRun()` : Numéro de l'itération en cours (commence à 1).
|
||||
* `progress.getTotalRuns()` : Nombre total d'itérations prévues.
|
||||
* `progress.isLastRun()` : true si c'est la dernière itération.
|
||||
* `progress.cancel()` : Interrompt immédiatement toutes les itérations restantes.
|
||||
* `progress.getContext()` : Récupère le contexte d'exécution d'origine.
|
||||
|
||||
---
|
||||
|
||||
> 📖 **Étape suivante** : Découvrez le fonctionnement interne dans [07_PLATFORM_AND_INJECTION.md](07_PLATFORM_AND_INJECTION.md).
|
||||
@@ -0,0 +1,60 @@
|
||||
# ⚙️ Plateforme & Injection CommandMap - betterMcCommands
|
||||
|
||||
Ce document explique le fonctionnement sous le capot de l'injection dynamique, de l'isolation des namespaces et de la gestion du cycle de vie des commandes.
|
||||
|
||||
---
|
||||
|
||||
## 📑 Sommaire
|
||||
- [1. Comment Fonctionne l'Injection Dynamique ?](#1-comment-fonctionne-linjection-dynamique-)
|
||||
- [2. Enveloppe Bukkit (`PaperCommandWrapper`)](#2-enveloppe-bukkit-papercommandwrapper)
|
||||
- [3. Désenregistrement à Chaud & Hot Reload](#3-désenregistrement-à-chaud--hot-reload)
|
||||
- [4. Isolation et Multi-Instances](#4-isolation-et-multi-instances)
|
||||
|
||||
---
|
||||
|
||||
## 1. Comment Fonctionne l'Injection Dynamique ?
|
||||
|
||||
Au lieu de forcer les développeurs à déclarer chaque commande dans le fichier `plugin.yml`, `betterMcCommands` utilise la classe `PaperCommandMapInjector` :
|
||||
|
||||
1. **Résolution de la `CommandMap`** :
|
||||
- Sur Paper / Bukkit moderne, `Bukkit.getServer().getCommandMap()` est interrogé.
|
||||
- Sur les versions antérieures, un fallback par réflexion inspecte l'attribut `commandMap` de `CraftServer`.
|
||||
2. **Accès à la table des commandes connues (`knownCommands`)** :
|
||||
- L'injecteur accède à la table interne `Map<String, Command>` de `SimpleCommandMap`.
|
||||
3. **Enregistrement de la commande racine et de ses alias** :
|
||||
- La commande est enregistrée avec le préfixe du plugin (ex: `/monplugin:commande`).
|
||||
- L'alias principal est rendu disponible directement (ex: `/commande`).
|
||||
|
||||
---
|
||||
|
||||
## 2. Enveloppe Bukkit (`PaperCommandWrapper`)
|
||||
|
||||
Pour que le serveur Minecraft sache router les commandes vers le moteur `betterMcCommands`, chaque `Command` racine est enveloppée dans une instance de `PaperCommandWrapper` qui étend `org.bukkit.command.Command` :
|
||||
|
||||
* `execute(CommandSender sender, String label, String[] args)` -> Transmet l'appel au `CommandDispatcher`.
|
||||
* `tabComplete(CommandSender sender, String alias, String[] args)` -> Calcule les suggestions via `CommandDispatcher.tabComplete(...)` et les retourne au client.
|
||||
|
||||
---
|
||||
|
||||
## 3. Désenregistrement à Chaud & Hot Reload
|
||||
|
||||
Lorsqu'un plugin est désactivé (`onDisable()`) ou rechargé, il est primordial de retirer proprement les commandes pour éviter les fuites de mémoire et les conflits d'alias.
|
||||
|
||||
L'appel à `commandsManager.unregisterAll()` :
|
||||
1. Dé-enregistre chaque wrapper de la `CommandMap`.
|
||||
2. Supprime toutes les entrées associées de la table `knownCommands`.
|
||||
3. Réinitialise le `CooldownManager` et le `ConfirmationManager`.
|
||||
4. Vide le bus d'événements `CommandEventManager`.
|
||||
|
||||
---
|
||||
|
||||
## 4. Isolation et Multi-Instances
|
||||
|
||||
Chaque plugin utilisant `betterMcCommands` peut posséder sa propre instance indépendante grâce à :
|
||||
|
||||
```java
|
||||
BetterMcCommands pluginA = BetterMcCommands.create(monPluginA);
|
||||
BetterMcCommands pluginB = BetterMcCommands.create(monPluginB);
|
||||
```
|
||||
|
||||
Chaque instance possède son propre namespace, son propre bus d'événements et sa propre table de cooldowns/confirmations sans interférence mutuelle.
|
||||
Reference in New Issue
Block a user