package fr.luc.bettermccommands.api; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.minimessage.MiniMessage; import org.bukkit.ChatColor; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.command.ConsoleCommandSender; import org.bukkit.entity.Player; import java.time.Duration; import java.util.*; /** * Contexte complet d'exécution d'une commande. * Contient l'émetteur, les arguments analysés et typés, les métadonnées et des utilitaires de réponse. */ public class CommandContext { private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); private final CommandSender sender; private final String label; private final String[] rawArgs; private final Map arguments; private final Map metadata; /** * Construit un nouveau contexte de commande. * * @param sender L'émetteur de la commande. * @param label Le nom ou alias utilisé pour invoquer la commande. * @param rawArgs Les arguments bruts sous forme de tableau de chaînes. * @param arguments La table des arguments résolus et typés. */ public CommandContext(CommandSender sender, String label, String[] rawArgs, Map arguments) { this.sender = Objects.requireNonNull(sender, "sender cannot be null"); this.label = label != null ? label : ""; this.rawArgs = rawArgs != null ? rawArgs : new String[0]; this.arguments = arguments != null ? new HashMap<>(arguments) : new HashMap<>(); this.metadata = new HashMap<>(); } /** * @return L'émetteur de la commande (Player, ConsoleCommandSender, etc.). */ public CommandSender getSender() { return sender; } /** * @return true si l'émetteur est un {@link Player}, sinon false. */ public boolean isPlayer() { return sender instanceof Player; } /** * @return true si l'émetteur est la console du serveur. */ public boolean isConsole() { return sender instanceof ConsoleCommandSender; } /** * Récupère le joueur émetteur de la commande. * * @return L'instance de {@link Player}. * @throws IllegalStateException si l'émetteur n'est pas un joueur. */ public Player getPlayer() { if (!isPlayer()) { throw new IllegalStateException("CommandSender is not a Player: " + sender.getClass().getSimpleName()); } return (Player) sender; } /** * Récupère la console émettrice de la commande. * * @return L'instance de {@link ConsoleCommandSender}. * @throws IllegalStateException si l'émetteur n'est pas la console. */ public ConsoleCommandSender getConsole() { if (!isConsole()) { throw new IllegalStateException("CommandSender is not a ConsoleCommandSender: " + sender.getClass().getSimpleName()); } return (ConsoleCommandSender) sender; } /** * @return Le label ou alias utilisé lors de l'exécution. */ public String getLabel() { return label; } /** * @return Le tableau des arguments bruts passés lors de l'appel. */ public String[] getRawArgs() { return rawArgs.clone(); } /** * @return Une vue non modifiable des arguments analysés. */ public Map getArguments() { return Collections.unmodifiableMap(arguments); } /** * Vérifie si un argument du nom donné a été renseigné et analysé. * * @param name Le nom de l'argument. * @return true si l'argument est présent, sinon false. */ public boolean hasArgument(String name) { return arguments.containsKey(name); } /** * Récupère un argument typé par son nom. * * @param name Le nom de l'argument. * @param type La classe attendue pour l'argument. * @param Le type générique. * @return L'instance de l'argument analysé. * @throws IllegalArgumentException si l'argument est manquant ou n'est pas du type attendu. */ @SuppressWarnings("unchecked") public T get(String name, Class type) { Object val = arguments.get(name); if (val == null) { throw new IllegalArgumentException("Argument '" + name + "' is missing from context."); } if (!type.isInstance(val)) { throw new IllegalArgumentException("Argument '" + name + "' is of type " + val.getClass().getSimpleName() + ", expected " + type.getSimpleName()); } return (T) val; } /** * Récupère un argument de manière optionnelle. * * @param name Le nom de l'argument. * @param type La classe attendue pour l'argument. * @param Le type générique. * @return Un {@link Optional} contenant la valeur si présente. */ @SuppressWarnings("unchecked") public Optional getOptional(String name, Class type) { Object val = arguments.get(name); if (val != null && type.isInstance(val)) { return Optional.of((T) val); } return Optional.empty(); } /** * Récupère un argument sous forme de chaîne de caractères. * * @param name Le nom de l'argument. * @return La chaîne analysée. */ public String getString(String name) { return get(name, String.class); } /** * Récupère un argument entier. * * @param name Le nom de l'argument. * @return L'entier analysé. */ public int getInt(String name) { return get(name, Integer.class); } /** * Récupère un argument double. * * @param name Le nom de l'argument. * @return Le double analysé. */ public double getDouble(String name) { return get(name, Double.class); } /** * Récupère un argument booléen. * * @param name Le nom de l'argument. * @return Le booléen analysé. */ public boolean getBoolean(String name) { return get(name, Boolean.class); } /** * Récupère un joueur cible ciblé par l'argument. * * @param name Le nom de l'argument. * @return Le joueur cible. */ public Player getTargetPlayer(String name) { return get(name, Player.class); } /** * Récupère un monde ciblé par l'argument. * * @param name Le nom de l'argument. * @return Le monde cible. */ public World getWorld(String name) { return get(name, World.class); } /** * Récupère une durée ciblée par l'argument. * * @param name Le nom de l'argument. * @return La durée analysée. */ public Duration getDuration(String name) { return get(name, Duration.class); } /** * Définit une métadonnée personnalisée dans le contexte d'exécution. * * @param key La clé de la métadonnée. * @param value La valeur associée. */ public void setMetadata(String key, Object value) { this.metadata.put(key, value); } /** * Récupère une métadonnée du contexte. * * @param key La clé. * @param type Le type attendu. * @param Le type générique. * @return Un {@link Optional} contenant la métadonnée si présente. */ @SuppressWarnings("unchecked") public Optional getMetadata(String key, Class type) { Object val = metadata.get(key); if (val != null && type.isInstance(val)) { return Optional.of((T) val); } return Optional.empty(); } /** * Envoie un message formaté avec MiniMessage à l'émetteur de la commande. * * @param miniMessageText Le texte formaté avec tags MiniMessage (ex: "Succès !"). */ public void reply(String miniMessageText) { if (miniMessageText == null || miniMessageText.isEmpty()) { return; } try { if (sender instanceof Audience) { ((Audience) sender).sendMessage(MINI_MESSAGE.deserialize(miniMessageText)); } else { sender.sendMessage(ChatColor.translateAlternateColorCodes('&', miniMessageText)); } } catch (Throwable t) { sender.sendMessage(miniMessageText); } } /** * Envoie un composant Kyori Adventure directement à l'émetteur. * * @param component Le composant texte Adventure. */ public void reply(Component component) { if (component == null) { return; } if (sender instanceof Audience) { ((Audience) sender).sendMessage(component); } else { sender.sendMessage(component.toString()); } } /** * Envoie un message de succès (préfixé en vert). * * @param message Le message de succès. */ public void replySuccess(String message) { reply("[] " + message + ""); } /** * Envoie un message d'erreur (préfixé en rouge). * * @param message Le message d'erreur. */ public void replyError(String message) { reply("[] " + message + ""); } /** * Envoie un message informatif (préfixé en bleu/aqua). * * @param message Le message d'information. */ public void replyInfo(String message) { reply("[] " + message + ""); } }