Ajout des fonctionnalités de validation de commandes/arguments, confirmation préalable (double validation) et répétition cadencée (CommandRepeater)
This commit is contained in:
@@ -18,7 +18,10 @@ Une bibliothèque Java moderne, fluide et événementielle permettant de créer,
|
||||
- [1. Commande de Démonstration Complète (`/commande-demo`)](#1-commande-de-démonstration-complète-commande-demo)
|
||||
- [2. Sous-Commandes Imbriquées](#2-sous-commandes-imbriquées)
|
||||
- [3. Arguments Gourmands (Greedy String)](#3-arguments-gourmands-greedy-string)
|
||||
- [4. Temps de Recharge (Cooldowns)](#4-temps-de-recharge-cooldowns)
|
||||
- [4. Système de Validation Métier (Pré-requis)](#4-système-de-validation-métier-pré-requis)
|
||||
- [5. Confirmation Préalable & Double Validation (`.requireConfirmation`)](#5-confirmation-préalable--double-validation-requireconfirmation)
|
||||
- [6. Répétition & Cadence de Commandes (`CommandRepeater`)](#6-répétition--cadence-de-commandes-commandrepeater)
|
||||
- [7. Temps de Recharge (Cooldowns)](#7-temps-de-recharge-cooldowns)
|
||||
- [🧩 Catalogue des Arguments Typés](#-catalogue-des-arguments-typés)
|
||||
- [🎯 Système d'Événements & Cycle de Vie](#-système-dévénements--cycle-de-vie)
|
||||
- [Liste des Événements Disponibles](#liste-des-événements-disponibles)
|
||||
@@ -31,6 +34,11 @@ Une bibliothèque Java moderne, fluide et événementielle permettant de créer,
|
||||
|
||||
- **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.
|
||||
@@ -58,10 +66,16 @@ flowchart TD
|
||||
PermCheck -- Valide --> CoolCheck{"Vérification Cooldown"}
|
||||
CoolCheck -- Actif --> EvtCool["Dispatch CommandCooldownEvent"]
|
||||
|
||||
CoolCheck -- Inactif --> ArgParse{"Parsing des Arguments Typés"}
|
||||
ArgParse -- Erreur format / manquant --> EvtSyntax["Dispatch CommandSyntaxErrorEvent"]
|
||||
CoolCheck -- Inactif --> ArgParse{"Parsing & Validation des Arguments"}
|
||||
ArgParse -- Erreur format / invalide --> EvtSyntax["Dispatch CommandSyntaxErrorEvent"]
|
||||
|
||||
ArgParse -- Succès --> EvtPre{"Dispatch CommandPreExecuteEvent"}
|
||||
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"]
|
||||
@@ -220,7 +234,75 @@ BetterMcCommands.builder("broadcast")
|
||||
|
||||
---
|
||||
|
||||
### 4. Temps de Recharge (Cooldowns)
|
||||
### 4. Système de Validation Métier (Pré-requis)
|
||||
|
||||
Vous pouvez attacher des validateurs à vos commandes et arguments :
|
||||
|
||||
```java
|
||||
// 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 :
|
||||
|
||||
```java
|
||||
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) :
|
||||
|
||||
```java
|
||||
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)
|
||||
|
||||
```java
|
||||
BetterMcCommands.builder("kit")
|
||||
@@ -264,6 +346,7 @@ BetterMcCommands.builder("kit")
|
||||
|---|---|:---:|
|
||||
| `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 |
|
||||
@@ -333,11 +416,22 @@ fr.luc.bettermccommands/
|
||||
│ ├── 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
|
||||
|
||||
@@ -27,6 +27,7 @@ public class BetterMcCommands {
|
||||
private final String pluginPrefix;
|
||||
private final CommandEventManager eventManager;
|
||||
private final CooldownManager cooldownManager;
|
||||
private final fr.luc.bettermccommands.confirmation.ConfirmationManager confirmationManager;
|
||||
private final CommandDispatcher dispatcher;
|
||||
private final PaperCommandMapInjector injector;
|
||||
private final Map<String, Command> registeredCommands = new ConcurrentHashMap<>();
|
||||
@@ -40,6 +41,7 @@ public class BetterMcCommands {
|
||||
this.pluginPrefix = pluginPrefix != null ? pluginPrefix : "bettermc";
|
||||
this.eventManager = new CommandEventManager();
|
||||
this.cooldownManager = new CooldownManager();
|
||||
this.confirmationManager = new fr.luc.bettermccommands.confirmation.ConfirmationManager();
|
||||
this.dispatcher = new CommandDispatcher(this);
|
||||
this.injector = new PaperCommandMapInjector();
|
||||
|
||||
@@ -155,6 +157,13 @@ public class BetterMcCommands {
|
||||
return cooldownManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le gestionnaire des confirmations d'exécution.
|
||||
*/
|
||||
public fr.luc.bettermccommands.confirmation.ConfirmationManager getConfirmationManager() {
|
||||
return confirmationManager;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le dispatcher d'exécution.
|
||||
*/
|
||||
|
||||
@@ -24,6 +24,12 @@ public class CommandNode {
|
||||
protected Duration cooldown = Duration.ZERO;
|
||||
protected String cooldownBypassPermission;
|
||||
|
||||
// Validation et confirmation
|
||||
protected final List<fr.luc.bettermccommands.validation.CommandValidator> validators = new ArrayList<>();
|
||||
protected boolean confirmationRequired = false;
|
||||
protected Duration confirmationTimeout = Duration.ofSeconds(15);
|
||||
protected String confirmationPrompt;
|
||||
|
||||
// Écouteurs d'événements locaux attachés directement à ce nœud
|
||||
protected final List<CommandEventListener<CommandPreExecuteEvent>> preExecuteListeners = new CopyOnWriteArrayList<>();
|
||||
protected final List<CommandEventListener<CommandPostExecuteEvent>> postExecuteListeners = new CopyOnWriteArrayList<>();
|
||||
@@ -225,6 +231,38 @@ public class CommandNode {
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
public void addValidator(fr.luc.bettermccommands.validation.CommandValidator validator) {
|
||||
this.validators.add(Objects.requireNonNull(validator, "Validator cannot be null"));
|
||||
}
|
||||
|
||||
public List<fr.luc.bettermccommands.validation.CommandValidator> getValidators() {
|
||||
return Collections.unmodifiableList(validators);
|
||||
}
|
||||
|
||||
public boolean isConfirmationRequired() {
|
||||
return confirmationRequired;
|
||||
}
|
||||
|
||||
public void setConfirmationRequired(boolean confirmationRequired) {
|
||||
this.confirmationRequired = confirmationRequired;
|
||||
}
|
||||
|
||||
public Duration getConfirmationTimeout() {
|
||||
return confirmationTimeout;
|
||||
}
|
||||
|
||||
public void setConfirmationTimeout(Duration confirmationTimeout) {
|
||||
this.confirmationTimeout = confirmationTimeout;
|
||||
}
|
||||
|
||||
public String getConfirmationPrompt() {
|
||||
return confirmationPrompt;
|
||||
}
|
||||
|
||||
public void setConfirmationPrompt(String confirmationPrompt) {
|
||||
this.confirmationPrompt = confirmationPrompt;
|
||||
}
|
||||
|
||||
// Gestion des écouteurs locaux
|
||||
public List<CommandEventListener<CommandPreExecuteEvent>> getPreExecuteListeners() {
|
||||
return preExecuteListeners;
|
||||
|
||||
@@ -5,6 +5,7 @@ import fr.luc.bettermccommands.api.suggestion.Suggestion;
|
||||
import fr.luc.bettermccommands.api.suggestion.SuggestionProvider;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
@@ -22,6 +23,7 @@ public class CommandArgument<T> {
|
||||
private Supplier<T> defaultValueSupplier;
|
||||
private SuggestionProvider customSuggestionProvider;
|
||||
private boolean greedy;
|
||||
private final java.util.List<fr.luc.bettermccommands.validation.ArgumentValidator<T>> validators = new java.util.ArrayList<>();
|
||||
|
||||
/**
|
||||
* Crée un argument obligatoire avec son nom et son type.
|
||||
@@ -173,6 +175,35 @@ public class CommandArgument<T> {
|
||||
return type.suggest(context, currentInput);
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un validateur personnalisé pour cet argument.
|
||||
*
|
||||
* @param validator Le validateur d'argument.
|
||||
* @return Cette instance d'argument.
|
||||
*/
|
||||
public CommandArgument<T> validate(fr.luc.bettermccommands.validation.ArgumentValidator<T> validator) {
|
||||
this.validators.add(Objects.requireNonNull(validator, "Validator cannot be null"));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une règle de validation simple basée sur un prédicat.
|
||||
*
|
||||
* @param predicate Le prédicat retournant true si la valeur est valide.
|
||||
* @param errorMessage Le message d'erreur en cas d'échec.
|
||||
* @return Cette instance d'argument.
|
||||
*/
|
||||
public CommandArgument<T> validate(java.util.function.Predicate<T> predicate, String errorMessage) {
|
||||
return validate(fr.luc.bettermccommands.validation.ArgumentValidator.of(predicate, errorMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return La liste des validateurs associés à cet argument.
|
||||
*/
|
||||
public java.util.List<fr.luc.bettermccommands.validation.ArgumentValidator<T>> getValidators() {
|
||||
return Collections.unmodifiableList(validators);
|
||||
}
|
||||
|
||||
/**
|
||||
* Retourne la représentation textuelle de l'argument pour l'aide.
|
||||
*
|
||||
|
||||
@@ -30,6 +30,11 @@ public abstract class AbstractCommandBuilder<B extends AbstractCommandBuilder<B,
|
||||
protected Duration cooldown = Duration.ZERO;
|
||||
protected String cooldownBypassPermission;
|
||||
|
||||
protected final List<fr.luc.bettermccommands.validation.CommandValidator> validators = new ArrayList<>();
|
||||
protected boolean confirmationRequired = false;
|
||||
protected Duration confirmationTimeout = Duration.ofSeconds(15);
|
||||
protected String confirmationPrompt;
|
||||
|
||||
protected final List<CommandEventListener<CommandPreExecuteEvent>> preExecuteListeners = new ArrayList<>();
|
||||
protected final List<CommandEventListener<CommandPostExecuteEvent>> postExecuteListeners = new ArrayList<>();
|
||||
protected final List<CommandEventListener<CommandPermissionDeniedEvent>> permissionDeniedListeners = new ArrayList<>();
|
||||
@@ -249,6 +254,61 @@ public abstract class AbstractCommandBuilder<B extends AbstractCommandBuilder<B,
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une règle de validation personnalisée à cette commande.
|
||||
*
|
||||
* @param validator Le validateur.
|
||||
* @return Cette instance du builder.
|
||||
*/
|
||||
public B validate(fr.luc.bettermccommands.validation.CommandValidator validator) {
|
||||
this.validators.add(Objects.requireNonNull(validator, "Validator cannot be null"));
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute une règle de validation simple basée sur un prédicat.
|
||||
*
|
||||
* @param predicate Le test de validation (retourne true si valide).
|
||||
* @param errorMessage Le message envoyé si le test échoue.
|
||||
* @return Cette instance du builder.
|
||||
*/
|
||||
public B validate(java.util.function.Predicate<CommandContext> predicate, String errorMessage) {
|
||||
return validate(fr.luc.bettermccommands.validation.CommandValidator.of(predicate, errorMessage));
|
||||
}
|
||||
|
||||
/**
|
||||
* Exige une confirmation préalable du joueur avant d'exécuter l'action (délai par défaut: 15s).
|
||||
*
|
||||
* @return Cette instance du builder.
|
||||
*/
|
||||
public B requireConfirmation() {
|
||||
return requireConfirmation(Duration.ofSeconds(15), null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exige une confirmation préalable du joueur avec un délai personnalisé.
|
||||
*
|
||||
* @param timeout La durée maximale pour confirmer.
|
||||
* @return Cette instance du builder.
|
||||
*/
|
||||
public B requireConfirmation(Duration timeout) {
|
||||
return requireConfirmation(timeout, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exige une confirmation préalable du joueur avec un délai et message personnalisés.
|
||||
*
|
||||
* @param timeout La durée maximale pour confirmer.
|
||||
* @param promptMessage Le message de demande de confirmation.
|
||||
* @return Cette instance du builder.
|
||||
*/
|
||||
public B requireConfirmation(Duration timeout, String promptMessage) {
|
||||
this.confirmationRequired = true;
|
||||
this.confirmationTimeout = timeout != null ? timeout : Duration.ofSeconds(15);
|
||||
this.confirmationPrompt = promptMessage;
|
||||
return (B) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Construit et configure l'instance du nœud de commande.
|
||||
*
|
||||
@@ -271,6 +331,13 @@ public abstract class AbstractCommandBuilder<B extends AbstractCommandBuilder<B,
|
||||
node.setCooldown(this.cooldown);
|
||||
node.setCooldownBypassPermission(this.cooldownBypassPermission);
|
||||
|
||||
node.setConfirmationRequired(this.confirmationRequired);
|
||||
node.setConfirmationTimeout(this.confirmationTimeout);
|
||||
node.setConfirmationPrompt(this.confirmationPrompt);
|
||||
for (fr.luc.bettermccommands.validation.CommandValidator validator : this.validators) {
|
||||
node.addValidator(validator);
|
||||
}
|
||||
|
||||
node.getPreExecuteListeners().addAll(this.preExecuteListeners);
|
||||
node.getPostExecuteListeners().addAll(this.postExecuteListeners);
|
||||
node.getPermissionDeniedListeners().addAll(this.permissionDeniedListeners);
|
||||
|
||||
@@ -0,0 +1,95 @@
|
||||
package fr.luc.bettermccommands.confirmation;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
import fr.luc.bettermccommands.api.CommandNode;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* Gestionnaire des confirmations et validations préalables à l'exécution de commandes sensibles.
|
||||
*/
|
||||
public class ConfirmationManager {
|
||||
|
||||
private final Map<UUID, PendingConfirmation> pendingConfirmations = new ConcurrentHashMap<>();
|
||||
|
||||
/**
|
||||
* Enregistre une demande de confirmation pour un joueur.
|
||||
*
|
||||
* @param player Le joueur devant confirmer.
|
||||
* @param node Le nœud de commande concerné.
|
||||
* @param context Le contexte de la commande.
|
||||
* @param timeout Le délai maximum accordé pour confirmer.
|
||||
*/
|
||||
public void requestConfirmation(Player player, CommandNode node, CommandContext context, Duration timeout) {
|
||||
if (player == null || node == null) return;
|
||||
Instant expiresAt = Instant.now().plus(timeout != null ? timeout : Duration.ofSeconds(15));
|
||||
pendingConfirmations.put(player.getUniqueId(), new PendingConfirmation(node, context, expiresAt));
|
||||
}
|
||||
|
||||
/**
|
||||
* Récupère la confirmation en attente pour un joueur s'il y en a une valide et non expirée.
|
||||
*
|
||||
* @param player Le joueur.
|
||||
* @return Un {@link Optional} contenant la confirmation valide.
|
||||
*/
|
||||
public Optional<PendingConfirmation> getPendingConfirmation(Player player) {
|
||||
if (player == null) return Optional.empty();
|
||||
PendingConfirmation pending = pendingConfirmations.get(player.getUniqueId());
|
||||
if (pending == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (pending.isExpired()) {
|
||||
pendingConfirmations.remove(player.getUniqueId());
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(pending);
|
||||
}
|
||||
|
||||
/**
|
||||
* Valide et consomme la confirmation en attente pour un joueur.
|
||||
*
|
||||
* @param player Le joueur confirmant l'action.
|
||||
* @return La {@link PendingConfirmation} validée, ou {@link Optional#empty()} si aucune confirmation active.
|
||||
*/
|
||||
public Optional<PendingConfirmation> consumeConfirmation(Player player) {
|
||||
if (player == null) return Optional.empty();
|
||||
PendingConfirmation pending = pendingConfirmations.remove(player.getUniqueId());
|
||||
if (pending == null || pending.isExpired()) {
|
||||
return Optional.empty();
|
||||
}
|
||||
return Optional.of(pending);
|
||||
}
|
||||
|
||||
/**
|
||||
* Annule la confirmation en attente pour un joueur.
|
||||
*
|
||||
* @param player Le joueur.
|
||||
*/
|
||||
public void cancelConfirmation(Player player) {
|
||||
if (player != null) {
|
||||
pendingConfirmations.remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Nettoie toutes les confirmations expirées en mémoire.
|
||||
*/
|
||||
public void cleanupExpired() {
|
||||
pendingConfirmations.entrySet().removeIf(entry -> entry.getValue().isExpired());
|
||||
}
|
||||
|
||||
/**
|
||||
* Supprime toutes les confirmations en mémoire.
|
||||
*/
|
||||
public void clearAll() {
|
||||
pendingConfirmations.clear();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package fr.luc.bettermccommands.confirmation;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
import fr.luc.bettermccommands.api.CommandNode;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Représente une demande de confirmation en attente d'approbation par un joueur.
|
||||
*/
|
||||
public class PendingConfirmation {
|
||||
|
||||
private final CommandNode node;
|
||||
private final CommandContext context;
|
||||
private final Instant expiresAt;
|
||||
|
||||
/**
|
||||
* Crée une nouvelle confirmation en attente.
|
||||
*
|
||||
* @param node Le nœud de commande en attente.
|
||||
* @param context Le contexte de commande capturé.
|
||||
* @param expiresAt La date/heure d'expiration.
|
||||
*/
|
||||
public PendingConfirmation(CommandNode node, CommandContext context, Instant expiresAt) {
|
||||
this.node = Objects.requireNonNull(node, "node cannot be null");
|
||||
this.context = Objects.requireNonNull(context, "context cannot be null");
|
||||
this.expiresAt = Objects.requireNonNull(expiresAt, "expiresAt cannot be null");
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le nœud de commande concerné.
|
||||
*/
|
||||
public CommandNode getNode() {
|
||||
return node;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le contexte d'exécution capturé lors de l'appel initial.
|
||||
*/
|
||||
public CommandContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return La date/heure d'expiration de la confirmation.
|
||||
*/
|
||||
public Instant getExpiresAt() {
|
||||
return expiresAt;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true si le délai de confirmation est dépassé, sinon false.
|
||||
*/
|
||||
public boolean isExpired() {
|
||||
return Instant.now().isAfter(expiresAt);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return La durée restante avant expiration.
|
||||
*/
|
||||
public Duration getRemaining() {
|
||||
Instant now = Instant.now();
|
||||
if (now.isAfter(expiresAt)) {
|
||||
return Duration.ZERO;
|
||||
}
|
||||
return Duration.between(now, expiresAt);
|
||||
}
|
||||
}
|
||||
@@ -131,6 +131,27 @@ public class DemoBaseCommand {
|
||||
)
|
||||
)
|
||||
|
||||
// 6. Sous-commande avec confirmation préalable (Double validation) : /demo delete-data
|
||||
.subcommand(BetterMcCommands.subBuilder("delete-data")
|
||||
.description("Action sensible nécessitant une confirmation")
|
||||
.permission("bettermc.demo.admin")
|
||||
.playerOnly()
|
||||
.requireConfirmation(Duration.ofSeconds(15), "<red><bold>ATTENTION</bold> : Cette action est irréversible ! Retapez <yellow>/demo delete-data</yellow> dans les 15s pour confirmer la suppression.</red>")
|
||||
.executes(context -> {
|
||||
context.replySuccess("Données supprimées avec succès après confirmation !");
|
||||
})
|
||||
)
|
||||
|
||||
// 7. Sous-commande avec validateur de pré-requis : /demo level-reward
|
||||
.subcommand(BetterMcCommands.subBuilder("level-reward")
|
||||
.description("Récompense réservée aux joueurs de niveau 5 ou plus")
|
||||
.playerOnly()
|
||||
.validate(ctx -> ctx.getPlayer().getLevel() >= 5, "Vous devez posséder au minimum le niveau d'expérience 5 pour réclamer cette récompense !")
|
||||
.executes(context -> {
|
||||
context.replySuccess("Félicitations ! Vous avez réclamé votre récompense de niveau.");
|
||||
})
|
||||
)
|
||||
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
package fr.luc.bettermccommands.event;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
import fr.luc.bettermccommands.api.CommandNode;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
import java.time.Duration;
|
||||
|
||||
/**
|
||||
* Événement déclenché lorsqu'une commande nécessite une confirmation avant d'être exécutée.
|
||||
* Permet de personnaliser le message de confirmation ou d'annuler la demande de confirmation.
|
||||
*/
|
||||
public class CommandConfirmationRequiredEvent extends CancellableCommandEvent {
|
||||
|
||||
private final Duration timeout;
|
||||
private String customPromptMessage;
|
||||
|
||||
/**
|
||||
* Crée l'événement de demande de confirmation.
|
||||
*
|
||||
* @param node Le nœud de commande concerné.
|
||||
* @param sender L'émetteur (joueur).
|
||||
* @param context Le contexte de commande capturé.
|
||||
* @param timeout Le délai imparti pour confirmer.
|
||||
*/
|
||||
public CommandConfirmationRequiredEvent(CommandNode node, CommandSender sender, CommandContext context, Duration timeout) {
|
||||
super(node, sender, context);
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return La durée impartie pour confirmer l'action.
|
||||
*/
|
||||
public Duration getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le message personnalisé de confirmation, ou {@code null} pour le message par défaut.
|
||||
*/
|
||||
public String getCustomPromptMessage() {
|
||||
return customPromptMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* Définit un message de confirmation personnalisé.
|
||||
*
|
||||
* @param customPromptMessage Le message au format MiniMessage ou couleurs Minecraft.
|
||||
*/
|
||||
public void setCustomPromptMessage(String customPromptMessage) {
|
||||
this.customPromptMessage = customPromptMessage;
|
||||
}
|
||||
}
|
||||
@@ -184,6 +184,44 @@ public class CommandDispatcher {
|
||||
// 6. Contexte final
|
||||
CommandContext fullContext = new CommandContext(sender, label, args, parsedArguments);
|
||||
|
||||
// 6.5. Vérification de la confirmation requise (Double validation)
|
||||
if (targetNode.isConfirmationRequired() && sender instanceof Player player) {
|
||||
Optional<fr.luc.bettermccommands.confirmation.PendingConfirmation> pendingOpt = manager.getConfirmationManager().getPendingConfirmation(player);
|
||||
if (pendingOpt.isPresent() && pendingOpt.get().getNode().getFullName().equalsIgnoreCase(targetNode.getFullName())) {
|
||||
// Confirmation reçue et validée !
|
||||
manager.getConfirmationManager().consumeConfirmation(player);
|
||||
} else {
|
||||
// Enregistrement de la confirmation en attente
|
||||
manager.getConfirmationManager().requestConfirmation(player, targetNode, fullContext, targetNode.getConfirmationTimeout());
|
||||
CommandConfirmationRequiredEvent confEvent = new CommandConfirmationRequiredEvent(
|
||||
targetNode, player, fullContext, targetNode.getConfirmationTimeout());
|
||||
dispatchEvent(targetNode, confEvent);
|
||||
|
||||
if (!confEvent.isCancelled()) {
|
||||
if (confEvent.getCustomPromptMessage() != null) {
|
||||
confEvent.reply(confEvent.getCustomPromptMessage());
|
||||
} else if (targetNode.getConfirmationPrompt() != null) {
|
||||
fullContext.reply(targetNode.getConfirmationPrompt());
|
||||
} else {
|
||||
long secs = targetNode.getConfirmationTimeout().toSeconds();
|
||||
fullContext.reply("<gold>⚠ <bold>Confirmation requise</bold> : </gold>" +
|
||||
"<yellow>Retapez la commande <aqua>/" + label + (args.length > 0 ? " " + String.join(" ", args) : "") +
|
||||
"</aqua> dans les <gold>" + secs + "s</gold> pour valider l'exécution.</yellow>");
|
||||
}
|
||||
}
|
||||
return CommandResult.CANCELLED;
|
||||
}
|
||||
}
|
||||
|
||||
// 6.6. Exécution des validateurs de commande (CommandValidator)
|
||||
for (fr.luc.bettermccommands.validation.CommandValidator validator : targetNode.getValidators()) {
|
||||
fr.luc.bettermccommands.validation.ValidationResult valRes = validator.validate(fullContext);
|
||||
if (!valRes.isValid()) {
|
||||
fullContext.replyError(valRes.getErrorMessage());
|
||||
return CommandResult.FAILED;
|
||||
}
|
||||
}
|
||||
|
||||
// 7. Événement PreExecute
|
||||
CommandPreExecuteEvent preEvent = new CommandPreExecuteEvent(targetNode, sender, fullContext);
|
||||
dispatchEvent(targetNode, preEvent);
|
||||
@@ -286,7 +324,14 @@ public class CommandDispatcher {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T parseArgumentValue(CommandArgument<T> arg, String input, CommandContext context) throws CommandArgumentParseException {
|
||||
return arg.getType().parse(arg.getName(), input, context);
|
||||
T parsed = arg.getType().parse(arg.getName(), input, context);
|
||||
for (fr.luc.bettermccommands.validation.ArgumentValidator<T> validator : arg.getValidators()) {
|
||||
fr.luc.bettermccommands.validation.ValidationResult result = validator.validate(parsed, context);
|
||||
if (!result.isValid()) {
|
||||
throw new CommandArgumentParseException(arg.getName(), input, arg.getType().getTypeName(), result.getErrorMessage());
|
||||
}
|
||||
}
|
||||
return parsed;
|
||||
}
|
||||
|
||||
private CommandResult handleSyntaxError(CommandNode node, CommandSender sender, CommandContext context,
|
||||
|
||||
@@ -0,0 +1,69 @@
|
||||
package fr.luc.bettermccommands.repeat;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
|
||||
/**
|
||||
* Informations de progression d'une exécution de commande répétée.
|
||||
*/
|
||||
public class CommandRepeatProgress {
|
||||
|
||||
private final CommandContext context;
|
||||
private final int currentRun;
|
||||
private final int totalRuns;
|
||||
private boolean cancelled = false;
|
||||
|
||||
/**
|
||||
* Crée un objet de suivi d'exécution de commande répétée.
|
||||
*
|
||||
* @param context Le contexte de la commande.
|
||||
* @param currentRun Le numéro de l'itération actuelle (1-indexed).
|
||||
* @param totalRuns Le nombre total d'itérations prévues.
|
||||
*/
|
||||
public CommandRepeatProgress(CommandContext context, int currentRun, int totalRuns) {
|
||||
this.context = context;
|
||||
this.currentRun = currentRun;
|
||||
this.totalRuns = totalRuns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le contexte d'exécution de la commande.
|
||||
*/
|
||||
public CommandContext getContext() {
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le numéro de l'itération en cours (ex: 1 pour la première).
|
||||
*/
|
||||
public int getCurrentRun() {
|
||||
return currentRun;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le nombre total d'exécutions programmées.
|
||||
*/
|
||||
public int getTotalRuns() {
|
||||
return totalRuns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true s'il s'agit de la dernière itération de la boucle.
|
||||
*/
|
||||
public boolean isLastRun() {
|
||||
return currentRun >= totalRuns;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true si la répétition a été interrompue.
|
||||
*/
|
||||
public boolean isCancelled() {
|
||||
return cancelled;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interrompt immédiatement les itérations restantes de la boucle.
|
||||
*/
|
||||
public void cancel() {
|
||||
this.cancelled = true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package fr.luc.bettermccommands.repeat;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
import org.bukkit.scheduler.BukkitTask;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Objects;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* Utilitaire permettant d'exécuter une commande ou une action associée de manière répétée / cadencée dans le temps.
|
||||
*/
|
||||
public class CommandRepeater {
|
||||
|
||||
/**
|
||||
* Répète une action liée au contexte de commande à intervalle régulier.
|
||||
*
|
||||
* @param plugin Le plugin Bukkit gérant les tâches.
|
||||
* @param context Le contexte de la commande.
|
||||
* @param totalRuns Le nombre total de répétitions.
|
||||
* @param interval L'intervalle de temps entre chaque exécution.
|
||||
* @param onRun L'action à exécuter à chaque itération.
|
||||
* @return L'instance de {@link BukkitTask} créée.
|
||||
*/
|
||||
public static BukkitTask repeat(Plugin plugin, CommandContext context, int totalRuns, Duration interval,
|
||||
Consumer<CommandRepeatProgress> onRun) {
|
||||
return repeat(plugin, context, totalRuns, interval, onRun, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Répète une action avec rappel final à l'issue de toutes les répétitions.
|
||||
*
|
||||
* @param plugin Le plugin Bukkit gérant les tâches.
|
||||
* @param context Le contexte de la commande.
|
||||
* @param totalRuns Le nombre total d'exécutions (ex: 5).
|
||||
* @param interval La durée entre deux itérations.
|
||||
* @param onRun L'action exécutée à chaque itération.
|
||||
* @param onComplete L'action finale appelée lorsque toutes les répétitions sont terminées (ou null).
|
||||
* @return L'instance de {@link BukkitTask} correspondante.
|
||||
*/
|
||||
public static BukkitTask repeat(Plugin plugin, CommandContext context, int totalRuns, Duration interval,
|
||||
Consumer<CommandRepeatProgress> onRun, Runnable onComplete) {
|
||||
Objects.requireNonNull(plugin, "plugin cannot be null");
|
||||
Objects.requireNonNull(context, "context cannot be null");
|
||||
Objects.requireNonNull(onRun, "onRun cannot be null");
|
||||
|
||||
long ticks = Math.max(1, (interval != null ? interval.toMillis() : 1000) / 50);
|
||||
AtomicInteger runCount = new AtomicInteger(0);
|
||||
|
||||
final BukkitTask[] taskHolder = new BukkitTask[1];
|
||||
|
||||
taskHolder[0] = Bukkit.getScheduler().runTaskTimer(plugin, () -> {
|
||||
int current = runCount.incrementAndGet();
|
||||
CommandRepeatProgress progress = new CommandRepeatProgress(context, current, totalRuns);
|
||||
|
||||
try {
|
||||
onRun.accept(progress);
|
||||
} catch (Exception e) {
|
||||
context.replyError("Erreur lors de l'exécution répétée : " + e.getMessage());
|
||||
e.printStackTrace();
|
||||
progress.cancel();
|
||||
}
|
||||
|
||||
if (progress.isCancelled() || current >= totalRuns) {
|
||||
if (taskHolder[0] != null) {
|
||||
taskHolder[0].cancel();
|
||||
}
|
||||
if (!progress.isCancelled() && onComplete != null) {
|
||||
try {
|
||||
onComplete.run();
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 0L, ticks);
|
||||
|
||||
return taskHolder[0];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
package fr.luc.bettermccommands.validation;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Validateur spécifique pour la valeur d'un argument typé.
|
||||
*
|
||||
* @param <T> Le type de l'argument.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface ArgumentValidator<T> {
|
||||
|
||||
/**
|
||||
* Valide la valeur de l'argument analysé.
|
||||
*
|
||||
* @param value La valeur convertie de l'argument.
|
||||
* @param context Le contexte d'exécution.
|
||||
* @return Le résultat de validation.
|
||||
*/
|
||||
ValidationResult validate(T value, CommandContext context);
|
||||
|
||||
/**
|
||||
* Crée un validateur d'argument simple à partir d'un prédicat.
|
||||
*
|
||||
* @param predicate Le prédicat testant la valeur.
|
||||
* @param errorMessage Le message d'erreur si le test échoue.
|
||||
* @param <T> Le type d'argument.
|
||||
* @return Le validateur configuré.
|
||||
*/
|
||||
static <T> ArgumentValidator<T> of(Predicate<T> predicate, String errorMessage) {
|
||||
return (value, context) -> {
|
||||
if (predicate.test(value)) {
|
||||
return ValidationResult.valid();
|
||||
}
|
||||
return ValidationResult.invalid(errorMessage);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package fr.luc.bettermccommands.validation;
|
||||
|
||||
import fr.luc.bettermccommands.api.CommandContext;
|
||||
|
||||
import java.util.function.Predicate;
|
||||
|
||||
/**
|
||||
* Validateur fonctionnel permettant de vérifier des conditions métier avant l'exécution d'une commande.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface CommandValidator {
|
||||
|
||||
/**
|
||||
* Valide le contexte d'exécution de la commande.
|
||||
*
|
||||
* @param context Le contexte complet de la commande.
|
||||
* @return Le {@link ValidationResult} indiquant le succès ou l'échec avec message.
|
||||
*/
|
||||
ValidationResult validate(CommandContext context);
|
||||
|
||||
/**
|
||||
* Crée un validateur à partir d'un prédicat simple et d'un message d'erreur.
|
||||
*
|
||||
* @param predicate Le prédicat retournant true si la condition est remplie.
|
||||
* @param errorMessage Le message d'erreur envoyé en cas d'échec.
|
||||
* @return L'instance de {@link CommandValidator}.
|
||||
*/
|
||||
static CommandValidator of(Predicate<CommandContext> predicate, String errorMessage) {
|
||||
return context -> {
|
||||
if (predicate.test(context)) {
|
||||
return ValidationResult.valid();
|
||||
}
|
||||
return ValidationResult.invalid(errorMessage);
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package fr.luc.bettermccommands.validation;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* Représente le résultat d'une validation de commande ou d'argument.
|
||||
*/
|
||||
public class ValidationResult {
|
||||
|
||||
private static final ValidationResult VALID = new ValidationResult(true, null);
|
||||
|
||||
private final boolean valid;
|
||||
private final String errorMessage;
|
||||
|
||||
private ValidationResult(boolean valid, String errorMessage) {
|
||||
this.valid = valid;
|
||||
this.errorMessage = errorMessage;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Un résultat indiquant que la validation a réussi.
|
||||
*/
|
||||
public static ValidationResult valid() {
|
||||
return VALID;
|
||||
}
|
||||
|
||||
/**
|
||||
* Crée un résultat d'échec de validation avec un message explicatif.
|
||||
*
|
||||
* @param errorMessage Le message d'erreur formaté (MiniMessage ou texte).
|
||||
* @return Le résultat d'échec.
|
||||
*/
|
||||
public static ValidationResult invalid(String errorMessage) {
|
||||
return new ValidationResult(false, Objects.requireNonNull(errorMessage, "errorMessage cannot be null"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return true si la validation est passée avec succès, sinon false.
|
||||
*/
|
||||
public boolean isValid() {
|
||||
return valid;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return Le message d'erreur en cas d'échec, ou {@code null} si valide.
|
||||
*/
|
||||
public String getErrorMessage() {
|
||||
return errorMessage;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package fr.luc.bettermccommands;
|
||||
|
||||
import fr.luc.bettermccommands.api.Command;
|
||||
import fr.luc.bettermccommands.api.CommandResult;
|
||||
import fr.luc.bettermccommands.argument.Arguments;
|
||||
import fr.luc.bettermccommands.validation.ValidationResult;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.DisplayName;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.mockito.Mockito;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
public class CommandValidationAndConfirmationTest {
|
||||
|
||||
private BetterMcCommands manager;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
manager = new BetterMcCommands("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("La validation de commande (CommandValidator) doit bloquer l'exécution si invalide")
|
||||
void testCommandValidation() {
|
||||
AtomicBoolean executed = new AtomicBoolean(false);
|
||||
AtomicBoolean allowExecution = new AtomicBoolean(false);
|
||||
|
||||
Command command = BetterMcCommands.builder("trade")
|
||||
.validate(ctx -> {
|
||||
if (!allowExecution.get()) {
|
||||
return ValidationResult.invalid("Échange temporairement indisponible.");
|
||||
}
|
||||
return ValidationResult.valid();
|
||||
})
|
||||
.executes(ctx -> executed.set(true))
|
||||
.build();
|
||||
|
||||
Player player = Mockito.mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
|
||||
// 1. Validation échoue
|
||||
CommandResult res1 = manager.getDispatcher().execute(command, player, "trade", new String[0]);
|
||||
assertEquals(CommandResult.FAILED, res1);
|
||||
assertFalse(executed.get());
|
||||
|
||||
// 2. Validation réussit
|
||||
allowExecution.set(true);
|
||||
CommandResult res2 = manager.getDispatcher().execute(command, player, "trade", new String[0]);
|
||||
assertEquals(CommandResult.SUCCESS, res2);
|
||||
assertTrue(executed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("La validation d'argument (ArgumentValidator) doit rejeter les entrées non conformes")
|
||||
void testArgumentValidation() {
|
||||
AtomicBoolean executed = new AtomicBoolean(false);
|
||||
|
||||
Command command = BetterMcCommands.builder("rename")
|
||||
.argument(Arguments.string("nom")
|
||||
.validate(nom -> !nom.equalsIgnoreCase("root"), "Le nom 'root' est interdit."))
|
||||
.executes(ctx -> executed.set(true))
|
||||
.build();
|
||||
|
||||
Player player = Mockito.mock(Player.class);
|
||||
when(player.getUniqueId()).thenReturn(UUID.randomUUID());
|
||||
|
||||
// Nom interdit "root" -> Erreur
|
||||
CommandResult res1 = manager.getDispatcher().execute(command, player, "rename", new String[]{"root"});
|
||||
assertEquals(CommandResult.SYNTAX_ERROR, res1);
|
||||
assertFalse(executed.get());
|
||||
|
||||
// Nom valide "Guerrier" -> Succès
|
||||
CommandResult res2 = manager.getDispatcher().execute(command, player, "rename", new String[]{"Guerrier"});
|
||||
assertEquals(CommandResult.SUCCESS, res2);
|
||||
assertTrue(executed.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
@DisplayName("Le système de confirmation doit demander confirmation puis exécuter à la seconde tentative")
|
||||
void testConfirmationWorkflow() {
|
||||
AtomicBoolean executed = new AtomicBoolean(false);
|
||||
|
||||
Command command = BetterMcCommands.builder("disband")
|
||||
.requireConfirmation(Duration.ofSeconds(10))
|
||||
.executes(ctx -> executed.set(true))
|
||||
.build();
|
||||
|
||||
Player player = Mockito.mock(Player.class);
|
||||
UUID uuid = UUID.randomUUID();
|
||||
when(player.getUniqueId()).thenReturn(uuid);
|
||||
|
||||
// 1ère tentative : Enregistre la confirmation et bloque l'exécution
|
||||
CommandResult res1 = manager.getDispatcher().execute(command, player, "disband", new String[0]);
|
||||
assertEquals(CommandResult.CANCELLED, res1);
|
||||
assertFalse(executed.get());
|
||||
assertTrue(manager.getConfirmationManager().getPendingConfirmation(player).isPresent());
|
||||
|
||||
// 2ème tentative immédiate : Valide et exécute
|
||||
CommandResult res2 = manager.getDispatcher().execute(command, player, "disband", new String[0]);
|
||||
assertEquals(CommandResult.SUCCESS, res2);
|
||||
assertTrue(executed.get());
|
||||
assertFalse(manager.getConfirmationManager().getPendingConfirmation(player).isPresent());
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user