From 0ba297a648182036961cbe7e603ef9336b057122 Mon Sep 17 00:00:00 2001 From: GeminiAntigravityCLI Date: Thu, 20 Aug 2026 17:10:42 +0200 Subject: [PATCH] refactor: clean base with WorldGuard, WorldEdit, and Vault dependencies --- build.gradle.kts | 3 + src/main/java/fr/luc/gamingcore/Main.java | 70 ++++- .../fr/luc/gamingcore/command/AppCommand.java | 262 ++++++++++++++++++ .../command/BukkitCommandWrapper.java | 37 +++ .../gamingcore/command/CommandBuilder.java | 107 +++++++ .../gamingcore/command/CommandContext.java | 109 ++++++++ .../gamingcore/command/CommandHandler.java | 6 + .../gamingcore/command/CommandManager.java | 101 +++++++ .../argument/ArgumentParseException.java | 8 + .../command/argument/ArgumentParser.java | 30 ++ .../command/argument/CommandArgument.java | 72 +++++ .../argument/parsers/DoubleArgument.java | 52 ++++ .../argument/parsers/IntegerArgument.java | 52 ++++ .../argument/parsers/PlayerArgument.java | 41 +++ .../argument/parsers/StringArgument.java | 66 +++++ .../command/cooldown/CooldownManager.java | 62 +++++ .../java/fr/luc/gamingcore/util/TextUtil.java | 56 ++++ src/main/resources/plugin.yml | 4 +- 18 files changed, 1136 insertions(+), 2 deletions(-) create mode 100644 src/main/java/fr/luc/gamingcore/command/AppCommand.java create mode 100644 src/main/java/fr/luc/gamingcore/command/BukkitCommandWrapper.java create mode 100644 src/main/java/fr/luc/gamingcore/command/CommandBuilder.java create mode 100644 src/main/java/fr/luc/gamingcore/command/CommandContext.java create mode 100644 src/main/java/fr/luc/gamingcore/command/CommandHandler.java create mode 100644 src/main/java/fr/luc/gamingcore/command/CommandManager.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/ArgumentParseException.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/ArgumentParser.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/CommandArgument.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/parsers/DoubleArgument.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/parsers/IntegerArgument.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/parsers/PlayerArgument.java create mode 100644 src/main/java/fr/luc/gamingcore/command/argument/parsers/StringArgument.java create mode 100644 src/main/java/fr/luc/gamingcore/command/cooldown/CooldownManager.java create mode 100644 src/main/java/fr/luc/gamingcore/util/TextUtil.java diff --git a/build.gradle.kts b/build.gradle.kts index f8ae3b8..bbf0029 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -23,6 +23,9 @@ dependencies { compileOnly("com.sk89q.worldguard:worldguard-bukkit:7.0.9") compileOnly("com.sk89q.worldedit:worldedit-bukkit:7.3.0") + // Vault API (Economy & Permissions) + compileOnly("com.github.MilkBowl:VaultAPI:1.7.1") + // Adventure platform for Bukkit (cross-version RGB, MiniMessage, Titles, ActionBars) implementation("net.kyori:adventure-platform-bukkit:4.3.4") implementation("net.kyori:adventure-text-minimessage:4.17.0") diff --git a/src/main/java/fr/luc/gamingcore/Main.java b/src/main/java/fr/luc/gamingcore/Main.java index 7d60088..e5606f4 100644 --- a/src/main/java/fr/luc/gamingcore/Main.java +++ b/src/main/java/fr/luc/gamingcore/Main.java @@ -1,23 +1,91 @@ package fr.luc.gamingcore; +import com.sk89q.worldguard.WorldGuard; +import com.sk89q.worldguard.protection.regions.RegionContainer; +import fr.luc.gamingcore.command.CommandManager; +import fr.luc.gamingcore.util.TextUtil; +import net.milkbowl.vault.economy.Economy; +import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; public final class Main extends JavaPlugin { private static Main instance; + private Economy economy; + private RegionContainer regionContainer; + private CommandManager commandManager; + @Override public void onEnable() { instance = this; - getLogger().info("GamingCore a ete active avec succes !"); + saveDefaultConfig(); + + // 1. Initialiser le préfixe de messages MiniMessage + TextUtil.setPrefix(getConfig().getString("prefix", "[Guilde] » ")); + + // 2. Connexion à WorldGuard + setupWorldGuard(); + + // 3. Connexion à Vault (Économie) + setupEconomy(); + + // 4. Initialisation du CommandManager dynamique + this.commandManager = new CommandManager(this); + + getLogger().info("GamingCore (Système de Guilde) a ete active avec succes !"); } @Override public void onDisable() { + if (commandManager != null) { + commandManager.shutdown(); + } getLogger().info("GamingCore a ete desactive !"); } + private void setupWorldGuard() { + try { + this.regionContainer = WorldGuard.getInstance().getPlatform().getRegionContainer(); + getLogger().info("Connexion avec WorldGuard 7 etablie avec succes !"); + } catch (Throwable t) { + getLogger().warning("Impossible d'initialiser WorldGuard : " + t.getMessage()); + } + } + + private void setupEconomy() { + if (getServer().getPluginManager().getPlugin("Vault") == null) { + getLogger().warning("Vault n'a pas ete detecte ! Le support economique sera desactive."); + return; + } + + RegisteredServiceProvider rsp = getServer().getServicesManager().getRegistration(Economy.class); + if (rsp == null) { + getLogger().warning("Aucun provider d'economie (ex: Essentials, TheNewEconomy) trouve via Vault !"); + return; + } + + this.economy = rsp.getProvider(); + getLogger().info("Connexion avec l'economie Vault (" + economy.getName() + ") etablie avec succes !"); + } + public static Main getInstance() { return instance; } + + public Economy getEconomy() { + return economy; + } + + public boolean hasEconomy() { + return economy != null; + } + + public RegionContainer getRegionContainer() { + return regionContainer; + } + + public CommandManager getCommandManager() { + return commandManager; + } } diff --git a/src/main/java/fr/luc/gamingcore/command/AppCommand.java b/src/main/java/fr/luc/gamingcore/command/AppCommand.java new file mode 100644 index 0000000..e3abc3a --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/AppCommand.java @@ -0,0 +1,262 @@ +package fr.luc.gamingcore.command; + +import fr.luc.gamingcore.command.argument.ArgumentParseException; +import fr.luc.gamingcore.command.argument.CommandArgument; +import fr.luc.gamingcore.command.cooldown.CooldownManager; +import fr.luc.gamingcore.util.TextUtil; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.*; +import java.util.logging.Level; +import java.util.logging.Logger; +import java.util.stream.Collectors; + +public class AppCommand { + + private static final Logger LOGGER = Logger.getLogger("GamingCore"); + + private final String name; + private final List aliases; + private final String description; + private final String permission; + private final String permissionMessage; + private final boolean playerOnly; + private final long cooldownMillis; + private final String cooldownBypassPermission; + private final List> arguments; + private final Map subCommands; + private final CommandHandler handler; + + public AppCommand(String name, List aliases, String description, String permission, + String permissionMessage, boolean playerOnly, long cooldownMillis, + String cooldownBypassPermission, List> arguments, + Map subCommands, CommandHandler handler) { + this.name = name.toLowerCase(); + this.aliases = aliases != null ? aliases : Collections.emptyList(); + this.description = description != null ? description : ""; + this.permission = permission; + this.permissionMessage = permissionMessage != null ? permissionMessage : "Tu n'as pas la permission d'exécuter cette commande."; + this.playerOnly = playerOnly; + this.cooldownMillis = cooldownMillis; + this.cooldownBypassPermission = cooldownBypassPermission != null ? cooldownBypassPermission : "gamingcore.cooldown.bypass"; + this.arguments = arguments != null ? arguments : Collections.emptyList(); + this.subCommands = subCommands != null ? subCommands : Collections.emptyMap(); + this.handler = handler; + } + + public String getName() { + return name; + } + + public List getAliases() { + return aliases; + } + + public String getDescription() { + return description; + } + + public String getPermission() { + return permission; + } + + public boolean isPlayerOnly() { + return playerOnly; + } + + public long getCooldownMillis() { + return cooldownMillis; + } + + public List> getArguments() { + return arguments; + } + + public Map getSubCommands() { + return subCommands; + } + + public AppCommand getSubCommand(String name) { + if (name == null) return null; + String lower = name.toLowerCase(); + AppCommand direct = subCommands.get(lower); + if (direct != null) return direct; + for (AppCommand sub : subCommands.values()) { + if (sub.getAliases().stream().anyMatch(a -> a.equalsIgnoreCase(lower))) { + return sub; + } + } + return null; + } + + public void execute(CommandSender sender, String label, String[] args, CooldownManager cooldownManager, String currentPath) { + String fullPath = currentPath.isEmpty() ? name : currentPath + " " + name; + + // 1. Permission check + if (permission != null && !permission.isEmpty() && !sender.hasPermission(permission)) { + TextUtil.sendMessage(sender, permissionMessage); + return; + } + + // 2. Player-only check + if (playerOnly && !(sender instanceof Player)) { + sender.sendMessage("Cette commande doit être exécutée par un joueur."); + return; + } + + // 3. Sub-command check + if (args.length > 0) { + AppCommand sub = getSubCommand(args[0]); + if (sub != null) { + String[] nextArgs = Arrays.copyOfRange(args, 1, args.length); + sub.execute(sender, args[0], nextArgs, cooldownManager, fullPath); + return; + } + } + + // 4. If no sub-command matched but handler is null, show help + if (handler == null) { + sendHelp(sender, fullPath); + return; + } + + Player player = sender instanceof Player ? (Player) sender : null; + + // 5. Cooldown check + if (player != null && cooldownMillis > 0 && !player.hasPermission(cooldownBypassPermission)) { + String cooldownKey = fullPath; + if (cooldownManager.isOnCooldown(player, cooldownKey)) { + double remaining = cooldownManager.getRemainingSeconds(player, cooldownKey); + TextUtil.sendMessage(player, "⏳ Veuillez patienter " + remaining + "s avant de réutiliser cette commande."); + return; + } + } + + // 6. Argument parsing + Map parsedArgs = new HashMap<>(); + CommandContext preliminaryContext = new CommandContext(sender, label, args, parsedArgs, fullPath); + + int argIndex = 0; + for (int i = 0; i < arguments.size(); i++) { + CommandArgument argument = arguments.get(i); + + if (argIndex >= args.length) { + if (argument.isRequired()) { + TextUtil.sendMessage(sender, "Syntaxe incorrecte : /" + fullPath + " " + getSyntax() + ""); + return; + } else { + parsedArgs.put(argument.getName().toLowerCase(), argument.getDefaultValue()); + continue; + } + } + + String inputString; + if (argument.isGreedy()) { + // Consume all remaining arguments + inputString = String.join(" ", Arrays.copyOfRange(args, argIndex, args.length)); + argIndex = args.length; + } else { + inputString = args[argIndex]; + argIndex++; + } + + try { + Object parsed = argument.getParser().parse(preliminaryContext, inputString); + parsedArgs.put(argument.getName().toLowerCase(), parsed); + } catch (ArgumentParseException e) { + TextUtil.sendMessage(sender, "" + e.getMessage() + ""); + return; + } + } + + // 7. Execution + CommandContext finalContext = new CommandContext(sender, label, args, parsedArgs, fullPath); + try { + handler.execute(finalContext); + + // Apply cooldown on success + if (player != null && cooldownMillis > 0 && !player.hasPermission(cooldownBypassPermission)) { + cooldownManager.applyCooldown(player, fullPath, cooldownMillis); + } + } catch (Exception e) { + TextUtil.sendMessage(sender, "Une erreur est survenue lors de l'exécution de la commande."); + LOGGER.log(Level.SEVERE, "Erreur lors de l'exécution de la commande " + fullPath, e); + } + } + + public List tabComplete(CommandSender sender, String label, String[] args, String currentPath) { + String fullPath = currentPath.isEmpty() ? name : currentPath + " " + name; + + if (permission != null && !permission.isEmpty() && !sender.hasPermission(permission)) { + return Collections.emptyList(); + } + + if (args.length == 0) { + return Collections.emptyList(); + } + + if (args.length == 1) { + String prefix = args[0].toLowerCase(); + List suggestions = new ArrayList<>(); + + // Suggest subcommands + for (AppCommand sub : subCommands.values()) { + if (sub.getPermission() == null || sub.getPermission().isEmpty() || sender.hasPermission(sub.getPermission())) { + if (sub.getName().toLowerCase().startsWith(prefix)) { + suggestions.add(sub.getName()); + } + } + } + + // Also check first argument completions if applicable + if (!arguments.isEmpty()) { + CommandContext ctx = new CommandContext(sender, label, args, Collections.emptyMap(), fullPath); + suggestions.addAll(arguments.get(0).getParser().complete(ctx, args[0])); + } + + return suggestions; + } + + // Check if first arg matches a subcommand + AppCommand sub = getSubCommand(args[0]); + if (sub != null) { + String[] nextArgs = Arrays.copyOfRange(args, 1, args.length); + return sub.tabComplete(sender, args[0], nextArgs, fullPath); + } + + // Argument completions + int targetArgIndex = args.length - 1; + if (targetArgIndex < arguments.size()) { + CommandArgument argument = arguments.get(targetArgIndex); + CommandContext ctx = new CommandContext(sender, label, args, Collections.emptyMap(), fullPath); + return argument.getParser().complete(ctx, args[targetArgIndex]); + } + + return Collections.emptyList(); + } + + public String getSyntax() { + if (arguments.isEmpty()) return ""; + return arguments.stream() + .map(CommandArgument::getFormattedSyntax) + .collect(Collectors.joining(" ")); + } + + public void sendHelp(CommandSender sender, String fullPath) { + TextUtil.sendRawMessage(sender, "═══════════ [ " + fullPath.toUpperCase() + " ] ═══════════"); + if (!description.isEmpty()) { + TextUtil.sendRawMessage(sender, " " + description + ""); + } + if (!subCommands.isEmpty()) { + for (AppCommand sub : subCommands.values()) { + if (sub.getPermission() == null || sub.getPermission().isEmpty() || sender.hasPermission(sub.getPermission())) { + String subSyntax = sub.getSyntax(); + String syntaxPart = subSyntax.isEmpty() ? "" : " " + subSyntax; + TextUtil.sendRawMessage(sender, " /" + fullPath + " " + sub.getName() + syntaxPart + " - " + sub.getDescription() + ""); + } + } + } + TextUtil.sendRawMessage(sender, "════════════════════════════════════════"); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/BukkitCommandWrapper.java b/src/main/java/fr/luc/gamingcore/command/BukkitCommandWrapper.java new file mode 100644 index 0000000..ab957f8 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/BukkitCommandWrapper.java @@ -0,0 +1,37 @@ +package fr.luc.gamingcore.command; + +import fr.luc.gamingcore.command.cooldown.CooldownManager; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; + +import java.util.List; + +public class BukkitCommandWrapper extends Command { + + private final AppCommand appCommand; + private final CooldownManager cooldownManager; + + public BukkitCommandWrapper(AppCommand appCommand, CooldownManager cooldownManager) { + super(appCommand.getName(), appCommand.getDescription(), "/" + appCommand.getName(), appCommand.getAliases()); + this.appCommand = appCommand; + this.cooldownManager = cooldownManager; + if (appCommand.getPermission() != null && !appCommand.getPermission().isEmpty()) { + setPermission(appCommand.getPermission()); + } + } + + @Override + public boolean execute(CommandSender sender, String commandLabel, String[] args) { + appCommand.execute(sender, commandLabel, args, cooldownManager, ""); + return true; + } + + @Override + public List tabComplete(CommandSender sender, String alias, String[] args) throws IllegalArgumentException { + return appCommand.tabComplete(sender, alias, args, ""); + } + + public AppCommand getAppCommand() { + return appCommand; + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/CommandBuilder.java b/src/main/java/fr/luc/gamingcore/command/CommandBuilder.java new file mode 100644 index 0000000..4c835f7 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/CommandBuilder.java @@ -0,0 +1,107 @@ +package fr.luc.gamingcore.command; + +import fr.luc.gamingcore.command.argument.CommandArgument; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +public class CommandBuilder { + + private final String name; + private final List aliases = new ArrayList<>(); + private String description = ""; + private String permission = null; + private String permissionMessage = null; + private boolean playerOnly = false; + private long cooldownMillis = 0L; + private String cooldownBypassPermission = "gamingcore.cooldown.bypass"; + private final List> arguments = new ArrayList<>(); + private final Map subCommands = new LinkedHashMap<>(); + private CommandHandler handler = null; + + private CommandBuilder(String name) { + this.name = Objects.requireNonNull(name, "Command name cannot be null").trim(); + } + + public static CommandBuilder create(String name) { + return new CommandBuilder(name); + } + + public CommandBuilder aliases(String... aliases) { + if (aliases != null) { + this.aliases.addAll(Arrays.asList(aliases)); + } + return this; + } + + public CommandBuilder description(String description) { + this.description = description != null ? description : ""; + return this; + } + + public CommandBuilder permission(String permission) { + this.permission = permission; + return this; + } + + public CommandBuilder permissionMessage(String message) { + this.permissionMessage = message; + return this; + } + + public CommandBuilder playerOnly() { + this.playerOnly = true; + return this; + } + + public CommandBuilder playerOnly(boolean playerOnly) { + this.playerOnly = playerOnly; + return this; + } + + public CommandBuilder cooldown(long amount, TimeUnit unit) { + this.cooldownMillis = unit.toMillis(amount); + return this; + } + + public CommandBuilder cooldownSeconds(int seconds) { + return cooldown(seconds, TimeUnit.SECONDS); + } + + public CommandBuilder cooldownBypass(String permission) { + this.cooldownBypassPermission = permission; + return this; + } + + public CommandBuilder argument(CommandArgument argument) { + if (argument != null) { + this.arguments.add(argument); + } + return this; + } + + public CommandBuilder subCommand(AppCommand subCommand) { + if (subCommand != null) { + this.subCommands.put(subCommand.getName().toLowerCase(), subCommand); + } + return this; + } + + public CommandBuilder subCommand(CommandBuilder subBuilder) { + if (subBuilder != null) { + AppCommand built = subBuilder.build(); + this.subCommands.put(built.getName().toLowerCase(), built); + } + return this; + } + + public CommandBuilder executes(CommandHandler handler) { + this.handler = handler; + return this; + } + + public AppCommand build() { + return new AppCommand(name, aliases, description, permission, permissionMessage, + playerOnly, cooldownMillis, cooldownBypassPermission, arguments, subCommands, handler); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/CommandContext.java b/src/main/java/fr/luc/gamingcore/command/CommandContext.java new file mode 100644 index 0000000..083df8d --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/CommandContext.java @@ -0,0 +1,109 @@ +package fr.luc.gamingcore.command; + +import fr.luc.gamingcore.util.TextUtil; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +import java.util.Collections; +import java.util.Map; + +public class CommandContext { + + private final CommandSender sender; + private final String label; + private final String[] rawArgs; + private final Map parsedArgs; + private final String fullCommandPath; + + public CommandContext(CommandSender sender, String label, String[] rawArgs, Map parsedArgs, String fullCommandPath) { + this.sender = sender; + this.label = label; + this.rawArgs = rawArgs != null ? rawArgs : new String[0]; + this.parsedArgs = parsedArgs != null ? parsedArgs : Collections.emptyMap(); + this.fullCommandPath = fullCommandPath; + } + + public CommandSender getSender() { + return sender; + } + + public boolean isPlayer() { + return sender instanceof Player; + } + + public boolean isConsole() { + return !isPlayer(); + } + + public Player getPlayer() { + return isPlayer() ? (Player) sender : null; + } + + public String getLabel() { + return label; + } + + public String[] getRawArgs() { + return rawArgs; + } + + public String getFullCommandPath() { + return fullCommandPath; + } + + public boolean has(String argName) { + return parsedArgs.containsKey(argName.toLowerCase()); + } + + @SuppressWarnings("unchecked") + public T get(String argName, Class clazz) { + Object value = parsedArgs.get(argName.toLowerCase()); + if (value == null) { + return null; + } + if (clazz.isInstance(value)) { + return (T) value; + } + throw new ClassCastException("L'argument '" + argName + "' est de type " + value.getClass().getSimpleName() + ", attendu: " + clazz.getSimpleName()); + } + + public Object get(String argName) { + return parsedArgs.get(argName.toLowerCase()); + } + + public String getString(String argName) { + return get(argName, String.class); + } + + public int getInt(String argName) { + Integer val = get(argName, Integer.class); + return val != null ? val : 0; + } + + public double getDouble(String argName) { + Double val = get(argName, Double.class); + return val != null ? val : 0.0; + } + + public Player getTargetPlayer(String argName) { + return get(argName, Player.class); + } + + public void reply(String message) { + TextUtil.sendMessage(sender, message); + } + + public void sendRaw(String message) { + TextUtil.sendRawMessage(sender, message); + } + + public void error(String errorMessage) { + TextUtil.sendMessage(sender, "" + errorMessage + ""); + } + + public void sendActionBar(String message) { + if (isPlayer()) { + TextUtil.sendActionBar(getPlayer(), message); + } + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/CommandHandler.java b/src/main/java/fr/luc/gamingcore/command/CommandHandler.java new file mode 100644 index 0000000..2e5cbc2 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/CommandHandler.java @@ -0,0 +1,6 @@ +package fr.luc.gamingcore.command; + +@FunctionalInterface +public interface CommandHandler { + void execute(CommandContext context) throws Exception; +} diff --git a/src/main/java/fr/luc/gamingcore/command/CommandManager.java b/src/main/java/fr/luc/gamingcore/command/CommandManager.java new file mode 100644 index 0000000..44e5a7d --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/CommandManager.java @@ -0,0 +1,101 @@ +package fr.luc.gamingcore.command; + +import fr.luc.gamingcore.Main; +import fr.luc.gamingcore.command.cooldown.CooldownManager; +import org.bukkit.Bukkit; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.SimpleCommandMap; +import org.bukkit.plugin.SimplePluginManager; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.logging.Level; + +public class CommandManager { + + private final Main plugin; + private final CooldownManager cooldownManager; + private final Map registeredCommands = new ConcurrentHashMap<>(); + private CommandMap commandMap; + + public CommandManager(Main plugin) { + this.plugin = plugin; + this.cooldownManager = new CooldownManager(); + this.commandMap = resolveCommandMap(); + } + + private CommandMap resolveCommandMap() { + // Try direct Bukkit.getCommandMap() (Paper API) + try { + Method method = Bukkit.getServer().getClass().getMethod("getCommandMap"); + return (CommandMap) method.invoke(Bukkit.getServer()); + } catch (Exception ignored) {} + + // Fallback: Reflection on SimplePluginManager + try { + Field field = SimplePluginManager.class.getDeclaredField("commandMap"); + field.setAccessible(true); + return (CommandMap) field.get(Bukkit.getPluginManager()); + } catch (Exception e) { + plugin.getLogger().log(Level.SEVERE, "Impossible de recuperer le CommandMap de Bukkit !", e); + return null; + } + } + + public void register(AppCommand appCommand) { + if (commandMap == null) { + commandMap = resolveCommandMap(); + if (commandMap == null) { + plugin.getLogger().severe("Impossible d'enregistrer la commande " + appCommand.getName() + " : CommandMap introuvable !"); + return; + } + } + + BukkitCommandWrapper wrapper = new BukkitCommandWrapper(appCommand, cooldownManager); + commandMap.register(plugin.getName().toLowerCase(), wrapper); + registeredCommands.put(appCommand.getName().toLowerCase(), wrapper); + + plugin.getLogger().info("Commande dynamique enregistree : /" + appCommand.getName() + " (alias: " + String.join(", ", appCommand.getAliases()) + ")"); + } + + public void register(CommandBuilder builder) { + if (builder != null) { + register(builder.build()); + } + } + + public void unregister(String commandName) { + if (commandName == null) return; + BukkitCommandWrapper wrapper = registeredCommands.remove(commandName.toLowerCase()); + if (wrapper != null && commandMap instanceof SimpleCommandMap) { + try { + Field knownCommandsField = SimpleCommandMap.class.getDeclaredField("knownCommands"); + knownCommandsField.setAccessible(true); + @SuppressWarnings("unchecked") + Map knownCommands = (Map) knownCommandsField.get(commandMap); + knownCommands.remove(wrapper.getName().toLowerCase()); + for (String alias : wrapper.getAliases()) { + knownCommands.remove(alias.toLowerCase()); + } + wrapper.unregister(commandMap); + } catch (Exception e) { + plugin.getLogger().log(Level.WARNING, "Erreur lors du desenregistrement de la commande " + commandName, e); + } + } + } + + public void shutdown() { + for (String name : registeredCommands.keySet()) { + unregister(name); + } + registeredCommands.clear(); + cooldownManager.cleanExpired(); + } + + public CooldownManager getCooldownManager() { + return cooldownManager; + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/ArgumentParseException.java b/src/main/java/fr/luc/gamingcore/command/argument/ArgumentParseException.java new file mode 100644 index 0000000..1dd7b51 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/ArgumentParseException.java @@ -0,0 +1,8 @@ +package fr.luc.gamingcore.command.argument; + +public class ArgumentParseException extends Exception { + + public ArgumentParseException(String message) { + super(message); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/ArgumentParser.java b/src/main/java/fr/luc/gamingcore/command/argument/ArgumentParser.java new file mode 100644 index 0000000..28a591c --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/ArgumentParser.java @@ -0,0 +1,30 @@ +package fr.luc.gamingcore.command.argument; + +import fr.luc.gamingcore.command.CommandContext; + +import java.util.List; + +@FunctionalInterface +public interface ArgumentParser { + + /** + * Parses the string input into the target typed object. + * + * @param ctx The current command execution context + * @param input The raw argument string entered by the user + * @return The parsed object + * @throws ArgumentParseException If the input is invalid + */ + T parse(CommandContext ctx, String input) throws ArgumentParseException; + + /** + * Provides dynamic tab completions for this argument. + * + * @param ctx The current command context + * @param input The current prefix typed by the user + * @return A list of suggestions + */ + default List complete(CommandContext ctx, String input) { + return List.of(); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/CommandArgument.java b/src/main/java/fr/luc/gamingcore/command/argument/CommandArgument.java new file mode 100644 index 0000000..6513730 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/CommandArgument.java @@ -0,0 +1,72 @@ +package fr.luc.gamingcore.command.argument; + +public class CommandArgument { + + private final String name; + private final String description; + private final ArgumentParser parser; + private final boolean required; + private final T defaultValue; + private final boolean greedy; + + public CommandArgument(String name, String description, ArgumentParser parser, boolean required, T defaultValue, boolean greedy) { + this.name = name; + this.description = description; + this.parser = parser; + this.required = required; + this.defaultValue = defaultValue; + this.greedy = greedy; + } + + public static CommandArgument required(String name, ArgumentParser parser) { + return new CommandArgument<>(name, "", parser, true, null, false); + } + + public static CommandArgument required(String name, String description, ArgumentParser parser) { + return new CommandArgument<>(name, description, parser, true, null, false); + } + + public static CommandArgument optional(String name, ArgumentParser parser) { + return new CommandArgument<>(name, "", parser, false, null, false); + } + + public static CommandArgument optional(String name, ArgumentParser parser, T defaultValue) { + return new CommandArgument<>(name, "", parser, false, defaultValue, false); + } + + public static CommandArgument optional(String name, String description, ArgumentParser parser, T defaultValue) { + return new CommandArgument<>(name, description, parser, false, defaultValue, false); + } + + public static CommandArgument greedy(String name, String description, ArgumentParser parser, boolean required, T defaultValue) { + return new CommandArgument<>(name, description, parser, required, defaultValue, true); + } + + public String getName() { + return name; + } + + public String getDescription() { + return description; + } + + public ArgumentParser getParser() { + return parser; + } + + public boolean isRequired() { + return required; + } + + public T getDefaultValue() { + return defaultValue; + } + + public boolean isGreedy() { + return greedy; + } + + public String getFormattedSyntax() { + return required ? "<" + name + ">" : "[" + name + "]"; + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/parsers/DoubleArgument.java b/src/main/java/fr/luc/gamingcore/command/argument/parsers/DoubleArgument.java new file mode 100644 index 0000000..22b2678 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/parsers/DoubleArgument.java @@ -0,0 +1,52 @@ +package fr.luc.gamingcore.command.argument.parsers; + +import fr.luc.gamingcore.command.CommandContext; +import fr.luc.gamingcore.command.argument.ArgumentParser; +import fr.luc.gamingcore.command.argument.ArgumentParseException; + +import java.util.List; + +public class DoubleArgument implements ArgumentParser { + + private final double min; + private final double max; + + public DoubleArgument() { + this(Double.MIN_VALUE, Double.MAX_VALUE); + } + + public DoubleArgument(double min, double max) { + this.min = min; + this.max = max; + } + + public static DoubleArgument decimal() { + return new DoubleArgument(); + } + + public static DoubleArgument min(double min) { + return new DoubleArgument(min, Double.MAX_VALUE); + } + + public static DoubleArgument range(double min, double max) { + return new DoubleArgument(min, max); + } + + @Override + public Double parse(CommandContext ctx, String input) throws ArgumentParseException { + try { + double value = Double.parseDouble(input.replace(',', '.')); + if (value < min || value > max) { + throw new ArgumentParseException("La valeur doit être comprise entre " + min + " et " + max + "."); + } + return value; + } catch (NumberFormatException e) { + throw new ArgumentParseException("'" + input + "' n'est pas un nombre décimal valide."); + } + } + + @Override + public List complete(CommandContext ctx, String input) { + return List.of(); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/parsers/IntegerArgument.java b/src/main/java/fr/luc/gamingcore/command/argument/parsers/IntegerArgument.java new file mode 100644 index 0000000..a0f87e6 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/parsers/IntegerArgument.java @@ -0,0 +1,52 @@ +package fr.luc.gamingcore.command.argument.parsers; + +import fr.luc.gamingcore.command.CommandContext; +import fr.luc.gamingcore.command.argument.ArgumentParser; +import fr.luc.gamingcore.command.argument.ArgumentParseException; + +import java.util.List; + +public class IntegerArgument implements ArgumentParser { + + private final int min; + private final int max; + + public IntegerArgument() { + this(Integer.MIN_VALUE, Integer.MAX_VALUE); + } + + public IntegerArgument(int min, int max) { + this.min = min; + this.max = max; + } + + public static IntegerArgument integer() { + return new IntegerArgument(); + } + + public static IntegerArgument min(int min) { + return new IntegerArgument(min, Integer.MAX_VALUE); + } + + public static IntegerArgument range(int min, int max) { + return new IntegerArgument(min, max); + } + + @Override + public Integer parse(CommandContext ctx, String input) throws ArgumentParseException { + try { + int value = Integer.parseInt(input); + if (value < min || value > max) { + throw new ArgumentParseException("Le nombre doit être compris entre " + min + " et " + max + "."); + } + return value; + } catch (NumberFormatException e) { + throw new ArgumentParseException("'" + input + "' n'est pas un nombre entier valide."); + } + } + + @Override + public List complete(CommandContext ctx, String input) { + return List.of(); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/parsers/PlayerArgument.java b/src/main/java/fr/luc/gamingcore/command/argument/parsers/PlayerArgument.java new file mode 100644 index 0000000..16a011e --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/parsers/PlayerArgument.java @@ -0,0 +1,41 @@ +package fr.luc.gamingcore.command.argument.parsers; + +import fr.luc.gamingcore.command.CommandContext; +import fr.luc.gamingcore.command.argument.ArgumentParser; +import fr.luc.gamingcore.command.argument.ArgumentParseException; +import org.bukkit.Bukkit; +import org.bukkit.entity.Player; + +import java.util.List; +import java.util.stream.Collectors; + +public class PlayerArgument implements ArgumentParser { + + public static PlayerArgument player() { + return new PlayerArgument(); + } + + @Override + public Player parse(CommandContext ctx, String input) throws ArgumentParseException { + if (input == null || input.isEmpty()) { + throw new ArgumentParseException("Veuillez spécifier un pseudo de joueur."); + } + Player target = Bukkit.getPlayerExact(input); + if (target == null) { + target = Bukkit.getPlayer(input); + } + if (target == null || !target.isOnline()) { + throw new ArgumentParseException("Le joueur '" + input + "' n'est pas connecté."); + } + return target; + } + + @Override + public List complete(CommandContext ctx, String input) { + String lower = input != null ? input.toLowerCase() : ""; + return Bukkit.getOnlinePlayers().stream() + .map(Player::getName) + .filter(name -> name.toLowerCase().startsWith(lower)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/argument/parsers/StringArgument.java b/src/main/java/fr/luc/gamingcore/command/argument/parsers/StringArgument.java new file mode 100644 index 0000000..047635b --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/argument/parsers/StringArgument.java @@ -0,0 +1,66 @@ +package fr.luc.gamingcore.command.argument.parsers; + +import fr.luc.gamingcore.command.CommandContext; +import fr.luc.gamingcore.command.argument.ArgumentParser; +import fr.luc.gamingcore.command.argument.ArgumentParseException; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; +import java.util.stream.Collectors; + +public class StringArgument implements ArgumentParser { + + private final List suggestions; + private final Supplier> dynamicSuggestions; + + public StringArgument() { + this.suggestions = null; + this.dynamicSuggestions = null; + } + + public StringArgument(String... suggestions) { + this.suggestions = Arrays.asList(suggestions); + this.dynamicSuggestions = null; + } + + public StringArgument(Supplier> dynamicSuggestions) { + this.suggestions = null; + this.dynamicSuggestions = dynamicSuggestions; + } + + public static StringArgument word() { + return new StringArgument(); + } + + public static StringArgument choices(String... choices) { + return new StringArgument(choices); + } + + public static StringArgument dynamic(Supplier> supplier) { + return new StringArgument(supplier); + } + + @Override + public String parse(CommandContext ctx, String input) throws ArgumentParseException { + if (input == null || input.trim().isEmpty()) { + throw new ArgumentParseException("Le texte ne peut pas être vide."); + } + return input; + } + + @Override + public List complete(CommandContext ctx, String input) { + List list = suggestions; + if (dynamicSuggestions != null) { + list = dynamicSuggestions.get(); + } + if (list == null || list.isEmpty()) { + return List.of(); + } + String lower = input != null ? input.toLowerCase() : ""; + return list.stream() + .filter(s -> s.toLowerCase().startsWith(lower)) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/fr/luc/gamingcore/command/cooldown/CooldownManager.java b/src/main/java/fr/luc/gamingcore/command/cooldown/CooldownManager.java new file mode 100644 index 0000000..0649686 --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/command/cooldown/CooldownManager.java @@ -0,0 +1,62 @@ +package fr.luc.gamingcore.command.cooldown; + +import org.bukkit.entity.Player; + +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.ConcurrentHashMap; + +public class CooldownManager { + + private final Map> cooldowns = new ConcurrentHashMap<>(); + + public boolean isOnCooldown(Player player, String commandKey) { + if (player == null || commandKey == null) return false; + long remaining = getRemainingMillis(player, commandKey); + return remaining > 0; + } + + public long getRemainingMillis(Player player, String commandKey) { + if (player == null || commandKey == null) return 0L; + Map userCooldowns = cooldowns.get(player.getUniqueId()); + if (userCooldowns == null) return 0L; + + Long expireAt = userCooldowns.get(commandKey.toLowerCase()); + if (expireAt == null) return 0L; + + long diff = expireAt - System.currentTimeMillis(); + if (diff <= 0) { + userCooldowns.remove(commandKey.toLowerCase()); + return 0L; + } + return diff; + } + + public double getRemainingSeconds(Player player, String commandKey) { + long millis = getRemainingMillis(player, commandKey); + if (millis <= 0) return 0.0; + return Math.round((millis / 1000.0) * 10.0) / 10.0; + } + + public void applyCooldown(Player player, String commandKey, long durationMillis) { + if (player == null || commandKey == null || durationMillis <= 0) return; + cooldowns.computeIfAbsent(player.getUniqueId(), k -> new ConcurrentHashMap<>()) + .put(commandKey.toLowerCase(), System.currentTimeMillis() + durationMillis); + } + + public void clearCooldown(Player player, String commandKey) { + if (player == null || commandKey == null) return; + Map userCooldowns = cooldowns.get(player.getUniqueId()); + if (userCooldowns != null) { + userCooldowns.remove(commandKey.toLowerCase()); + } + } + + public void cleanExpired() { + long now = System.currentTimeMillis(); + cooldowns.forEach((uuid, map) -> { + map.entrySet().removeIf(entry -> entry.getValue() <= now); + }); + cooldowns.entrySet().removeIf(entry -> entry.getValue().isEmpty()); + } +} diff --git a/src/main/java/fr/luc/gamingcore/util/TextUtil.java b/src/main/java/fr/luc/gamingcore/util/TextUtil.java new file mode 100644 index 0000000..3534f7c --- /dev/null +++ b/src/main/java/fr/luc/gamingcore/util/TextUtil.java @@ -0,0 +1,56 @@ +package fr.luc.gamingcore.util; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.minimessage.MiniMessage; +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.bukkit.command.CommandSender; +import org.bukkit.entity.Player; + +public final class TextUtil { + + private static final MiniMessage MINI_MESSAGE = MiniMessage.miniMessage(); + private static final LegacyComponentSerializer AMPERSAND_SERIALIZER = LegacyComponentSerializer.legacyAmpersand(); + + private static String prefix = "[Guilde] » "; + + private TextUtil() {} + + public static void setPrefix(String newPrefix) { + if (newPrefix != null) { + prefix = newPrefix; + } + } + + public static String getPrefix() { + return prefix; + } + + /** + * Parses a string containing MiniMessage tags or legacy color codes into a Component. + */ + public static Component parse(String input) { + if (input == null || input.isEmpty()) { + return Component.empty(); + } + String formatted = input.replace('§', '&'); + if (formatted.contains("&")) { + return AMPERSAND_SERIALIZER.deserialize(formatted); + } + return MINI_MESSAGE.deserialize(input); + } + + public static void sendMessage(CommandSender sender, String message) { + if (sender == null || message == null) return; + sender.sendMessage(parse(prefix + message)); + } + + public static void sendRawMessage(CommandSender sender, String message) { + if (sender == null || message == null) return; + sender.sendMessage(parse(message)); + } + + public static void sendActionBar(Player player, String message) { + if (player == null || message == null) return; + player.sendActionBar(parse(message)); + } +} diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml index 91e5db0..24bbe8f 100644 --- a/src/main/resources/plugin.yml +++ b/src/main/resources/plugin.yml @@ -3,11 +3,13 @@ version: 1.0.0 main: fr.luc.gamingcore.Main api-version: '1.16' author: Luc -description: GamingCore Plugin +description: GamingCore Plugin - Système de Guilde depend: - WorldGuard + - Vault softdepend: - WorldEdit +