docs: ajout de la suite de documentation complète dans /docs et mise à jour du README

This commit is contained in:
2026-08-23 18:04:36 +02:00
parent a2ccf0eb8c
commit 81edbea01d
8 changed files with 821 additions and 0 deletions
+14
View File
@@ -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")`).
+127
View File
@@ -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).
+128
View File
@@ -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).
+155
View File
@@ -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).
+120
View File
@@ -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).
+103
View File
@@ -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).
+114
View File
@@ -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).
+60
View File
@@ -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.