diff --git a/pandalib-bungee-commands/pom.xml b/pandalib-bungee-commands/pom.xml new file mode 100644 index 0000000..204c1e0 --- /dev/null +++ b/pandalib-bungee-commands/pom.xml @@ -0,0 +1,48 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-bungee-commands + jar + + + + bungeecord-repo + https://oss.sonatype.org/content/repositories/snapshots + + + + + + fr.pandacube.lib + pandalib-util + ${project.version} + + + fr.pandacube.lib + pandalib-chat + ${project.version} + + + fr.pandacube.lib + pandalib-commands + ${project.version} + + + + net.md-5 + bungeecord-api + ${bungeecord.version} + provided + + + + + \ No newline at end of file diff --git a/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java new file mode 100644 index 0000000..53fdbf1 --- /dev/null +++ b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierCommand.java @@ -0,0 +1,128 @@ +package fr.pandacube.lib.bungee.commands; + +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; +import fr.pandacube.lib.commands.BrigadierCommand; +import fr.pandacube.lib.commands.SuggestionsSupplier; +import fr.pandacube.lib.reflect.Reflect; +import fr.pandacube.lib.util.Log; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.plugin.Command; +import net.md_5.bungee.api.plugin.TabExecutor; + +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; + +public abstract class BungeeBrigadierCommand extends BrigadierCommand { + + protected BungeeBrigadierDispatcher dispatcher; + + public BungeeBrigadierCommand(BungeeBrigadierDispatcher d) { + if (d == null) { + throw new IllegalStateException("BungeeBrigadierDispatcher not provided."); + } + dispatcher = d; + + LiteralCommandNode commandNode; + String[] aliases; + + try { + commandNode = buildCommand().build(); + postBuildCommand(commandNode); + aliases = getAliases(); + } catch (Exception e) { + Log.severe("Exception encountered when building Brigadier command " + getClass().getName(), e); + return; + } + if (aliases == null) + aliases = new String[0]; + + dispatcher.register(commandNode); + + // still have to be registered for console + ProxyServer.getInstance().getPluginManager().registerCommand(dispatcher.plugin, new CommandRelay(commandNode.getLiteral())); + + for (String alias : aliases) { + dispatcher.register(literal(alias) + .requires(commandNode.getRequirement()) + .executes(commandNode.getCommand()) + .redirect(commandNode) + .build() + ); + + ProxyServer.getInstance().getPluginManager().registerCommand(dispatcher.plugin, new CommandRelay(alias)); + } + + } + + private class CommandRelay extends Command implements TabExecutor { + private final String alias; + public CommandRelay(String alias) { + super(alias); + this.alias = alias; + } + @Override + public void execute(CommandSender sender, String[] args) { + dispatcher.execute(sender, alias + (args.length == 0 ? "" : (" " + String.join(" ", args)))); + } + @Override + public Iterable onTabComplete(CommandSender sender, String[] args) { + + String cursor = "/" + alias + " " + String.join(" ", args); + + StringRange supportedRange = StringRange.between(cursor.lastIndexOf(' ') + 1, cursor.length()); + + Suggestions suggestions = dispatcher.getSuggestions(sender, cursor.substring(1)); + if (!suggestions.getRange().equals(supportedRange)) + return Collections.emptyList(); + + return suggestions.getList() + .stream() + .filter(s -> s.getRange().equals(supportedRange)) + .map(Suggestion::getText) + .collect(Collectors.toList()); + } + } + + + + + + + + + + + public boolean isConsole(CommandSender sender) { + return ProxyServer.getInstance().getConsole().equals(sender); + } + public boolean isPlayer(CommandSender sender) { + return sender instanceof ProxiedPlayer; + } + public Predicate hasPermission(String permission) { + return sender -> sender.hasPermission(permission); + } + + + + + + protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions) { + return wrapSuggestions(suggestions, Function.identity()); + } + + +} \ No newline at end of file diff --git a/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java new file mode 100644 index 0000000..46cdbc6 --- /dev/null +++ b/pandalib-bungee-commands/src/main/java/fr/pandacube/lib/bungee/commands/BungeeBrigadierDispatcher.java @@ -0,0 +1,72 @@ +package fr.pandacube.lib.bungee.commands; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.context.SuggestionContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import fr.pandacube.lib.chat.Chat; +import fr.pandacube.lib.commands.BrigadierCommand; +import fr.pandacube.lib.commands.BrigadierDispatcher; +import fr.pandacube.lib.commands.BrigadierSuggestionsUtil; +import fr.pandacube.lib.util.Log; +import net.kyori.adventure.text.ComponentLike; +import net.md_5.bungee.api.CommandSender; +import net.md_5.bungee.api.ProxyServer; +import net.md_5.bungee.api.connection.ProxiedPlayer; +import net.md_5.bungee.api.event.ChatEvent; +import net.md_5.bungee.api.plugin.Listener; +import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.event.EventHandler; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +public class BungeeBrigadierDispatcher extends BrigadierDispatcher implements Listener { + + + + + /* package */ final Plugin plugin; + + public BungeeBrigadierDispatcher(Plugin pl) { + plugin = pl; + ProxyServer.getInstance().getPluginManager().registerListener(plugin, this); + } + + + + + + @EventHandler + public void onChat(ChatEvent event) { + if (!event.getMessage().startsWith("/")) + return; + + String commandLine = event.getMessage().substring(1); + + String commandName = commandLine.split(" ", -1)[0]; + + if (getDispatcher().getRoot().getChild(commandName) == null) + return; + + event.setCancelled(true); + + ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> execute((ProxiedPlayer) event.getSender(), commandLine)); + + } + + + @Override + protected void sendSenderMessage(CommandSender sender, ComponentLike message) { + sender.sendMessage(Chat.toBungee(message.asComponent())); + } +} diff --git a/pandalib-cli/pom.xml b/pandalib-cli/pom.xml index 0c2f5a6..0d974e9 100644 --- a/pandalib-cli/pom.xml +++ b/pandalib-cli/pom.xml @@ -32,12 +32,16 @@ pandalib-core ${project.version} - fr.pandacube.lib pandalib-reflect ${project.version} + + fr.pandacube.lib + pandalib-commands + ${project.version} + net.md-5 @@ -50,12 +54,6 @@ ${bungeecord.version} - - fr.pandacube.lib - pandalib-commands - ${project.version} - - diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java deleted file mode 100644 index 1eb692c..0000000 --- a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/BrigadierCommand.java +++ /dev/null @@ -1,139 +0,0 @@ -package fr.pandacube.lib.cli; - -import java.util.Arrays; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import com.mojang.brigadier.arguments.ArgumentType; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.builder.RequiredArgumentBuilder; -import com.mojang.brigadier.context.CommandContext; -import com.mojang.brigadier.context.ParsedCommandNode; -import com.mojang.brigadier.suggestion.Suggestion; -import com.mojang.brigadier.suggestion.SuggestionProvider; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import com.mojang.brigadier.tree.LiteralCommandNode; - -import fr.pandacube.lib.chat.ChatStatic; -import fr.pandacube.lib.commands.SuggestionsSupplier; -import fr.pandacube.lib.util.Log; -import fr.pandacube.lib.reflect.Reflect; - -public abstract class BrigadierCommand extends ChatStatic { - - public BrigadierCommand() { - LiteralArgumentBuilder builder = buildCommand(); - String[] aliases = getAliases(); - if (aliases == null) - aliases = new String[0]; - - LiteralCommandNode commandNode = BrigadierDispatcher.instance.register(builder); - - - for (String alias : aliases) { - BrigadierDispatcher.instance.register(literal(alias) - .requires(commandNode.getRequirement()) - .executes(commandNode.getCommand()) - .redirect(commandNode) - ); - - } - - } - - protected abstract LiteralArgumentBuilder buildCommand(); - - protected String[] getAliases() { - return new String[0]; - } - - - - - public static LiteralArgumentBuilder literal(String name) { - return LiteralArgumentBuilder.literal(name); - } - - public static RequiredArgumentBuilder argument(String name, ArgumentType type) { - return RequiredArgumentBuilder.argument(name, type); - } - - - public static boolean isLiteralParsed(CommandContext context, String literal) { - for (ParsedCommandNode node : context.getNodes()) { - if (!(node.getNode() instanceof LiteralCommandNode)) - continue; - if (((LiteralCommandNode)node.getNode()).getLiteral().equals(literal)) - return true; - } - return false; - } - - public static T tryGetArgument(CommandContext context, String argument, Class type) { - return tryGetArgument(context, argument, type, null); - } - - public static T tryGetArgument(CommandContext context, String argument, Class type, T deflt) { - try { - return context.getArgument(argument, type); - } catch (IllegalArgumentException e) { - return deflt; - } - } - - - - - protected static SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions) { - return (context, builder) -> { - Object sender = context.getSource(); - String message = builder.getInput(); - try { - int tokenStartPos = builder.getStart(); - - int firstSpacePos = message.indexOf(" "); - String[] args = (firstSpacePos + 1 > tokenStartPos - 1) ? new String[0] - : message.substring(firstSpacePos + 1, tokenStartPos - 1).split(" ", -1); - args = Arrays.copyOf(args, args.length + 1); - args[args.length - 1] = message.substring(tokenStartPos); - - List results = suggestions.getSuggestions(sender, args.length - 1, args[args.length - 1], args); - - for (String s : results) { - if (s != null) - builder.suggest(s); - } - } catch (Throwable e) { - Log.severe("Error while tab-completing '" + message/* + "' for " + sender.getName()*/, e); - } - return completableFutureSuggestionsKeepsOriginalOrdering(builder); - }; - } - - - - - public static CompletableFuture completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) { - return CompletableFuture.completedFuture( - BrigadierDispatcher.createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder)) - ); - } - - @SuppressWarnings("unchecked") - private static List getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) { - try { - return (List) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder); - } catch (ReflectiveOperationException e) { - throw new RuntimeException(e); - } - } - - - - - - - - -} \ No newline at end of file diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/BrigadierDispatcher.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/BrigadierDispatcher.java deleted file mode 100644 index 1d68bee..0000000 --- a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/BrigadierDispatcher.java +++ /dev/null @@ -1,158 +0,0 @@ -package fr.pandacube.lib.cli; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.concurrent.CompletableFuture; - -import com.mojang.brigadier.CommandDispatcher; -import com.mojang.brigadier.ParseResults; -import com.mojang.brigadier.builder.LiteralArgumentBuilder; -import com.mojang.brigadier.context.CommandContextBuilder; -import com.mojang.brigadier.context.StringRange; -import com.mojang.brigadier.context.SuggestionContext; -import com.mojang.brigadier.exceptions.CommandSyntaxException; -import com.mojang.brigadier.suggestion.Suggestion; -import com.mojang.brigadier.suggestion.Suggestions; -import com.mojang.brigadier.suggestion.SuggestionsBuilder; -import com.mojang.brigadier.tree.CommandNode; -import com.mojang.brigadier.tree.LiteralCommandNode; - -import fr.pandacube.lib.util.Log; -import jline.console.completer.Completer; - -public class BrigadierDispatcher implements Completer { - - public static final BrigadierDispatcher instance = new BrigadierDispatcher(); - - - - - - - - private final CommandDispatcher dispatcher; - - private final Object sender = new Object(); - - public BrigadierDispatcher() { - dispatcher = new CommandDispatcher<>(); - } - - - - /* package */ LiteralCommandNode register(LiteralArgumentBuilder node) { - return dispatcher.register(node); - } - - - public int execute(String commandWithoutSlash) { - ParseResults parsed = dispatcher.parse(commandWithoutSlash, sender); - - try { - return dispatcher.execute(parsed); - } catch (CommandSyntaxException e) { - Log.severe("Erreur d’utilisation de la commande : " + e.getMessage()); - return 0; - } catch (Throwable e) { - Log.severe("Erreur lors de l’exécution de la commande : ", e); - return 0; - } - - } - - - @Override - public int complete(String buffer, int cursor, List candidates) { - - String bufferBeforeCursor = buffer.substring(0, cursor); - - Suggestions completeResult = getSuggestions(bufferBeforeCursor); - - completeResult.getList().stream() - .map(Suggestion::getText) - .forEach(candidates::add); - - return completeResult.getRange().getStart(); - } - - /* package */ Suggestions getSuggestions(String buffer) { - ParseResults parsed = dispatcher.parse(buffer, sender); - try { - CompletableFuture futureSuggestions = buildSuggestionBrigadier(parsed); - return futureSuggestions.join(); - } catch (Throwable e) { - Log.severe("Erreur d’exécution des suggestions :\n" + e.getMessage(), e); - return Suggestions.empty().join(); - } - } - - - - CompletableFuture buildSuggestionBrigadier(ParseResults parsed) { - int cursor = parsed.getReader().getTotalLength(); - final CommandContextBuilder context = parsed.getContext(); - - final SuggestionContext nodeBeforeCursor = context.findSuggestionContext(cursor); - final CommandNode parent = nodeBeforeCursor.parent; - final int start = Math.min(nodeBeforeCursor.startPos, cursor); - - final String fullInput = parsed.getReader().getString(); - final String truncatedInput = fullInput.substring(0, cursor); - @SuppressWarnings("unchecked") final CompletableFuture[] futures = new CompletableFuture[parent.getChildren().size()]; - int i = 0; - for (final CommandNode node : parent.getChildren()) { - CompletableFuture future = Suggestions.empty(); - try { - future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start)); - } catch (final CommandSyntaxException ignored) { - } - futures[i++] = future; - } - - final CompletableFuture result = new CompletableFuture<>(); - CompletableFuture.allOf(futures).thenRun(() -> { - final List suggestions = new ArrayList<>(); - for (final CompletableFuture future : futures) { - suggestions.add(future.join()); - } - result.complete(mergeSuggestionsOriginalOrdering(fullInput, suggestions)); - }); - return result; - } - - // inspired from com.mojang.brigadier.suggestion.Suggestions#merge, but without the sorting part - public static Suggestions mergeSuggestionsOriginalOrdering(String input, Collection suggestions) { - if (suggestions.isEmpty()) { - return new Suggestions(StringRange.at(0), new ArrayList<>(0)); - } else if (suggestions.size() == 1) { - return suggestions.iterator().next(); - } - - final List texts = new ArrayList<>(); - for (final Suggestions suggestions1 : suggestions) { - texts.addAll(suggestions1.getList()); - } - return createSuggestionsOriginalOrdering(input, texts); - } - - // inspired from com.mojang.brigadier.suggestion.Suggestions#create, but without the sorting part - public static Suggestions createSuggestionsOriginalOrdering(String command, Collection suggestions) { - if (suggestions.isEmpty()) { - return new Suggestions(StringRange.at(0), new ArrayList<>(0)); - } - int start = Integer.MAX_VALUE; - int end = Integer.MIN_VALUE; - for (final Suggestion suggestion : suggestions) { - start = Math.min(suggestion.getRange().getStart(), start); - end = Math.max(suggestion.getRange().getEnd(), end); - } - final StringRange range = new StringRange(start, end); - final List texts = new ArrayList<>(suggestions.size()); - for (final Suggestion suggestion : suggestions) { - texts.add(suggestion.expand(command, range)); - } - return new Suggestions(range, texts); - } - -} diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLI.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLI.java index fc8a02c..a889676 100644 --- a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLI.java +++ b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLI.java @@ -3,6 +3,8 @@ package fr.pandacube.lib.cli; import java.io.IOException; import java.util.logging.Logger; +import fr.pandacube.lib.cli.commands.CLIBrigadierDispatcher; +import fr.pandacube.lib.cli.log.CLILogger; import jline.console.ConsoleReader; import org.fusesource.jansi.AnsiConsole; @@ -47,7 +49,7 @@ public class CLI { reader = new ConsoleReader(); reader.setBellEnabled(false); reader.setPrompt("\r>"); - reader.addCompleter(BrigadierDispatcher.instance); + reader.addCompleter(CLIBrigadierDispatcher.instance); // configuration du formatteur pour le logger System.setProperty("net.md_5.bungee.log-date-format", "yyyy-MM-dd HH:mm:ss"); @@ -78,7 +80,7 @@ public class CLI { if (line.trim().equals("")) continue; String cmdLine = line; - new Thread(() -> BrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start(); + new Thread(() -> CLIBrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start(); } } catch (IOException e) { diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/commands/CLIBrigadierCommand.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/commands/CLIBrigadierCommand.java new file mode 100644 index 0000000..ec7ce22 --- /dev/null +++ b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/commands/CLIBrigadierCommand.java @@ -0,0 +1,81 @@ +package fr.pandacube.lib.cli.commands; + +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.function.Predicate; + +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.LiteralCommandNode; + +import fr.pandacube.lib.commands.BrigadierCommand; +import fr.pandacube.lib.commands.BrigadierSuggestionsUtil; +import fr.pandacube.lib.commands.SuggestionsSupplier; +import fr.pandacube.lib.util.Log; + +public abstract class CLIBrigadierCommand extends BrigadierCommand { + + public CLIBrigadierCommand() { + LiteralCommandNode commandNode = buildCommand().build(); + postBuildCommand(commandNode); + String[] aliases = getAliases(); + if (aliases == null) + aliases = new String[0]; + + CLIBrigadierDispatcher.instance.register(commandNode); + + + for (String alias : aliases) { + CLIBrigadierDispatcher.instance.register(literal(alias) + .requires(commandNode.getRequirement()) + .executes(commandNode.getCommand()) + .redirect(commandNode) + .build() + ); + } + } + + protected abstract LiteralArgumentBuilder buildCommand(); + + protected String[] getAliases() { + return new String[0]; + } + + + + + + public boolean isPlayer(Object sender) { + return false; + } + + public boolean isConsole(Object sender) { + return true; + } + + public Predicate hasPermission(String permission) { + return sender -> true; + } + + + + + + protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions) { + return wrapSuggestions(suggestions, Function.identity()); + } + + + + + + + + + +} \ No newline at end of file diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/commands/CLIBrigadierDispatcher.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/commands/CLIBrigadierDispatcher.java new file mode 100644 index 0000000..a49e347 --- /dev/null +++ b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/commands/CLIBrigadierDispatcher.java @@ -0,0 +1,50 @@ +package fr.pandacube.lib.cli.commands; + +import java.util.List; + +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; + +import fr.pandacube.lib.chat.Chat; +import fr.pandacube.lib.commands.BrigadierDispatcher; +import fr.pandacube.lib.util.Log; +import jline.console.completer.Completer; +import net.kyori.adventure.text.ComponentLike; + +public class CLIBrigadierDispatcher extends BrigadierDispatcher implements Completer { + + public static final CLIBrigadierDispatcher instance = new CLIBrigadierDispatcher(); + + + private static final Object sender = new Object(); + + + public int execute(String commandWithoutSlash) { + return execute(sender, commandWithoutSlash); + } + + + @Override + public int complete(String buffer, int cursor, List candidates) { + + String bufferBeforeCursor = buffer.substring(0, cursor); + + Suggestions completeResult = getSuggestions(bufferBeforeCursor); + + completeResult.getList().stream() + .map(Suggestion::getText) + .forEach(candidates::add); + + return completeResult.getRange().getStart(); + } + + public Suggestions getSuggestions(String buffer) { + return getSuggestions(sender, buffer); + } + + + @Override + protected void sendSenderMessage(Object sender, ComponentLike message) { + Log.info(Chat.chatComponent(message).getLegacyText()); + } +} diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLILogger.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/log/CLILogger.java similarity index 84% rename from pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLILogger.java rename to pandalib-cli/src/main/java/fr/pandacube/lib/cli/log/CLILogger.java index 79d6e33..f2e16a3 100644 --- a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/CLILogger.java +++ b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/log/CLILogger.java @@ -1,10 +1,12 @@ -package fr.pandacube.lib.cli; +package fr.pandacube.lib.cli.log; import java.io.PrintStream; import java.util.logging.Handler; import java.util.logging.Level; import java.util.logging.Logger; +import fr.pandacube.lib.cli.CLI; +import fr.pandacube.lib.util.Log; import net.md_5.bungee.log.ColouredWriter; import net.md_5.bungee.log.ConciseFormatter; import net.md_5.bungee.log.LoggingOutputStream; @@ -13,7 +15,7 @@ public class CLILogger { private static Logger logger = null; - /* package */ static synchronized Logger getLogger(CLI cli) { + public static synchronized Logger getLogger(CLI cli) { if (logger == null) { logger = Logger.getGlobal(); logger.setLevel(Level.ALL); @@ -30,7 +32,8 @@ public class CLILogger { System.setErr(new PrintStream(new LoggingOutputStream(logger, Level.SEVERE), true)); System.setOut(new PrintStream(new LoggingOutputStream(logger, Level.INFO), true)); - + + Log.setLogger(logger); } return logger; } diff --git a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/DailyLogRotateFileHandler.java b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/log/DailyLogRotateFileHandler.java similarity index 98% rename from pandalib-cli/src/main/java/fr/pandacube/lib/cli/DailyLogRotateFileHandler.java rename to pandalib-cli/src/main/java/fr/pandacube/lib/cli/log/DailyLogRotateFileHandler.java index ad65d20..9a940f6 100644 --- a/pandalib-cli/src/main/java/fr/pandacube/lib/cli/DailyLogRotateFileHandler.java +++ b/pandalib-cli/src/main/java/fr/pandacube/lib/cli/log/DailyLogRotateFileHandler.java @@ -1,4 +1,4 @@ -package fr.pandacube.lib.cli; +package fr.pandacube.lib.cli.log; import java.io.BufferedWriter; import java.io.File; diff --git a/pandalib-commands/pom.xml b/pandalib-commands/pom.xml index eac787a..7a3e21a 100644 --- a/pandalib-commands/pom.xml +++ b/pandalib-commands/pom.xml @@ -31,6 +31,16 @@ pandalib-util ${project.version} + + fr.pandacube.lib + pandalib-chat + ${project.version} + + + fr.pandacube.lib + pandalib-reflect + ${project.version} + com.mojang diff --git a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java new file mode 100644 index 0000000..f824753 --- /dev/null +++ b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierCommand.java @@ -0,0 +1,186 @@ +package fr.pandacube.lib.commands; + +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.LiteralCommandNode; +import fr.pandacube.lib.reflect.Reflect; +import fr.pandacube.lib.util.Log; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.function.Function; +import java.util.function.Predicate; + +/** + * Abstract class that holds the logic of a specific command to be integrated in a Brigadier command dispatcher. + * Subclasses may use any mechanism to integrate this command in the environment’s Brigadier instance, during the + * instantiation of this object. + * @param the command source (or command sender) type. + */ +public abstract class BrigadierCommand { + + + + + + + protected abstract LiteralArgumentBuilder buildCommand(); + + /** + * Method to implement if the reference to the command node has to be known when building the subcommands. + * @param commandNode the command node builded from {@link #buildCommand()}. + */ + protected void postBuildCommand(LiteralCommandNode commandNode) { + // default implementation does nothing. + } + + protected String[] getAliases() { + return new String[0]; + } + + + + + + + + + + + + + + + public LiteralArgumentBuilder literal(String name) { + return LiteralArgumentBuilder.literal(name); + } + public RequiredArgumentBuilder argument(String name, ArgumentType type) { + return RequiredArgumentBuilder.argument(name, type); + } + + + + + + + + + public abstract boolean isPlayer(S sender); + + public abstract boolean isConsole(S sender); + + public Predicate isPlayer() { + return this::isPlayer; + } + + public Predicate isConsole() { + return this::isConsole; + } + + public abstract Predicate hasPermission(String permission); + + + + + + + + + + public static boolean isLiteralParsed(CommandContext context, String literal) { + for (ParsedCommandNode node : context.getNodes()) { + if (node.getNode() instanceof LiteralCommandNode lNode + && lNode.getLiteral().equals(literal)) + return true; + } + return false; + } + + + + + + + + + public static T tryGetArgument(CommandContext context, String argument, Class type) { + return tryGetArgument(context, argument, type, Function.identity(), null); + } + + public static T tryGetArgument(CommandContext context, String argument, Class sourceType, Function transformIfFound) { + return tryGetArgument(context, argument, sourceType, transformIfFound, null); + } + + public static T tryGetArgument(CommandContext context, String argument, Class type, T deflt) { + return tryGetArgument(context, argument, type, Function.identity(), deflt); + } + + public static T tryGetArgument(CommandContext context, String argument, Class sourceType, Function transformIfFound, T deflt) { + ST sourceValue; + try { + sourceValue = context.getArgument(argument, sourceType); + } catch (IllegalArgumentException e) { + return deflt; + } + return transformIfFound.apply(sourceValue); + } + + + + + + + + + + + public static CommandSyntaxException newCommandException(String message) { + return new SimpleCommandExceptionType(new LiteralMessage(message)).create(); + } + + + + + + + + + protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions, Function senderUnwrapper) { + return (context, builder) -> { + AS sender = senderUnwrapper.apply(context.getSource()); + String message = builder.getInput(); + try { + int tokenStartPos = builder.getStart(); + + int firstSpacePos = message.indexOf(" "); + String[] args = (firstSpacePos + 1 > tokenStartPos - 1) ? new String[0] + : message.substring(firstSpacePos + 1, tokenStartPos - 1).split(" ", -1); + args = Arrays.copyOf(args, args.length + 1); + args[args.length - 1] = message.substring(tokenStartPos); + + for (String s : suggestions.getSuggestions(sender, args.length - 1, args[args.length - 1], args)) { + if (s != null) + builder.suggest(s); + } + } catch (Throwable e) { + Log.severe("Error while tab-completing '" + message + "' for " + sender, e); + } + return BrigadierSuggestionsUtil.completableFutureSuggestionsKeepsOriginalOrdering(builder); + }; + } + +} diff --git a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java new file mode 100644 index 0000000..7a42bc4 --- /dev/null +++ b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierDispatcher.java @@ -0,0 +1,73 @@ +package fr.pandacube.lib.commands; + +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.tree.LiteralCommandNode; +import fr.pandacube.lib.chat.Chat; +import fr.pandacube.lib.util.Log; +import net.kyori.adventure.text.ComponentLike; + +import java.util.concurrent.CompletableFuture; + +public abstract class BrigadierDispatcher { + + + private final CommandDispatcher dispatcher = new CommandDispatcher<>(); + + + + public void register(LiteralCommandNode node) { + dispatcher.getRoot().addChild(node); + } + + + + + public CommandDispatcher getDispatcher() { + return dispatcher; + } + + + + + + public int execute(S sender, String commandWithoutSlash) { + ParseResults parsed = dispatcher.parse(commandWithoutSlash, sender); + + try { + return dispatcher.execute(parsed); + } catch (CommandSyntaxException e) { + sendSenderMessage(sender, Chat.failureText("Error while using the command: " + e.getMessage())); + return 0; + } catch (Throwable e) { + sendSenderMessage(sender, Chat.failureText("Error while running the command: " + e.getMessage())); + Log.severe(e); + return 0; + } + + } + + + + public Suggestions getSuggestions(S sender, String buffer) { + ParseResults parsed = dispatcher.parse(buffer, sender); + try { + CompletableFuture futureSuggestions = BrigadierSuggestionsUtil.buildSuggestionBrigadier(parsed); + return futureSuggestions.join(); + } catch (Throwable e) { + sendSenderMessage(sender, Chat.failureText("Error while generating the suggestions:\n" + e.getMessage())); + Log.severe(e); + return Suggestions.empty().join(); + } + } + + + + + + + protected abstract void sendSenderMessage(S sender, ComponentLike message); + +} diff --git a/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java new file mode 100644 index 0000000..995c181 --- /dev/null +++ b/pandalib-commands/src/main/java/fr/pandacube/lib/commands/BrigadierSuggestionsUtil.java @@ -0,0 +1,115 @@ +package fr.pandacube.lib.commands; + +import com.mojang.brigadier.ParseResults; +import com.mojang.brigadier.context.CommandContextBuilder; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.context.SuggestionContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.CommandNode; +import fr.pandacube.lib.reflect.Reflect; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.concurrent.CompletableFuture; + +/** + * Utility methods to replace some functionalities of Brigadier, especialy suggestion sorting that we don’t like. + */ +public class BrigadierSuggestionsUtil { + + + + public static CompletableFuture buildSuggestionBrigadier(ParseResults parsed) { + int cursor = parsed.getReader().getTotalLength(); + final CommandContextBuilder context = parsed.getContext(); + + final SuggestionContext nodeBeforeCursor = context.findSuggestionContext(cursor); + final CommandNode parent = nodeBeforeCursor.parent; + final int start = Math.min(nodeBeforeCursor.startPos, cursor); + + final String fullInput = parsed.getReader().getString(); + final String truncatedInput = fullInput.substring(0, cursor); + @SuppressWarnings("unchecked") final CompletableFuture[] futures = new CompletableFuture[parent.getChildren().size()]; + int i = 0; + for (final CommandNode node : parent.getChildren()) { + CompletableFuture future = Suggestions.empty(); + try { + future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start)); + } catch (final CommandSyntaxException ignored) { + } + futures[i++] = future; + } + + final CompletableFuture result = new CompletableFuture<>(); + CompletableFuture.allOf(futures).thenRun(() -> { + final List suggestions = new ArrayList<>(); + for (final CompletableFuture future : futures) { + suggestions.add(future.join()); + } + result.complete(mergeSuggestionsOriginalOrdering(fullInput, suggestions)); + }); + return result; + } + + // inspired from com.mojang.brigadier.suggestion.Suggestions#merge, but without the sorting part + public static Suggestions mergeSuggestionsOriginalOrdering(String input, Collection suggestions) { + if (suggestions.isEmpty()) { + return new Suggestions(StringRange.at(0), new ArrayList<>(0)); + } else if (suggestions.size() == 1) { + return suggestions.iterator().next(); + } + + final List texts = new ArrayList<>(); + for (final Suggestions suggestions1 : suggestions) { + texts.addAll(suggestions1.getList()); + } + return createSuggestionsOriginalOrdering(input, texts); + } + + + + + // inspired from com.mojang.brigadier.suggestion.Suggestions#create, but without the sorting part + public static Suggestions createSuggestionsOriginalOrdering(String command, Collection suggestions) { + if (suggestions.isEmpty()) { + return new Suggestions(StringRange.at(0), new ArrayList<>(0)); + } + int start = Integer.MAX_VALUE; + int end = Integer.MIN_VALUE; + for (final Suggestion suggestion : suggestions) { + start = Math.min(suggestion.getRange().getStart(), start); + end = Math.max(suggestion.getRange().getEnd(), end); + } + final StringRange range = new StringRange(start, end); + final List texts = new ArrayList<>(suggestions.size()); + for (final Suggestion suggestion : suggestions) { + texts.add(suggestion.expand(command, range)); + } + return new Suggestions(range, texts); + } + + + + + + public static CompletableFuture completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) { + return CompletableFuture.completedFuture( + createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder)) + ); + } + + @SuppressWarnings("unchecked") + private static List getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) { + try { + return (List) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder); + } catch (ReflectiveOperationException e) { + throw new RuntimeException(e); + } + } + + +} diff --git a/pandalib-paper-commands/pom.xml b/pandalib-paper-commands/pom.xml new file mode 100644 index 0000000..4711358 --- /dev/null +++ b/pandalib-paper-commands/pom.xml @@ -0,0 +1,72 @@ + + + + pandalib-parent + fr.pandacube.lib + 1.0-SNAPSHOT + + 4.0.0 + + pandalib-paper-commands + jar + + + + papermc + https://papermc.io/repo/repository/maven-public/ + + + + + + fr.pandacube.lib + pandalib-chat + ${project.version} + + + net.kyori + adventure-api + + + net.kyori + adventure-text-serializer-plain + + + + + + fr.pandacube.lib + pandalib-commands + ${project.version} + + + + fr.pandacube.lib + pandalib-paper-reflect + ${project.version} + + + + fr.pandacube.lib + pandalib-util + ${project.version} + + + + + io.papermc.paper + paper-api + ${paper.version}-SNAPSHOT + provided + + + io.papermc.paper + paper-mojangapi + ${paper.version}-SNAPSHOT + provided + + + + \ No newline at end of file diff --git a/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java b/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java new file mode 100644 index 0000000..63dcf03 --- /dev/null +++ b/pandalib-paper-commands/src/main/java/fr/pandacube/lib/paper/commands/PaperBrigadierCommand.java @@ -0,0 +1,460 @@ +package fr.pandacube.lib.paper.commands; + +import com.destroystokyo.paper.brigadier.BukkitBrigadierCommandSource; +import com.mojang.brigadier.CommandDispatcher; +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.context.ParsedCommandNode; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.exceptions.SimpleCommandExceptionType; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import fr.pandacube.lib.chat.Chat; +import fr.pandacube.lib.commands.BrigadierCommand; +import fr.pandacube.lib.commands.SuggestionsSupplier; +import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftNamespacedKey; +import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftServer; +import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.CraftVector; +import fr.pandacube.lib.paper.reflect.wrapper.craftbukkit.VanillaCommandWrapper; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.BlockPosArgument; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Commands; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ComponentArgument; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Coordinates; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntityArgument; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.EntitySelector; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.GameProfileArgument; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.ResourceLocationArgument; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.commands.Vec3Argument; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.core.BlockPos; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.resources.ResourceLocation; +import fr.pandacube.lib.paper.reflect.wrapper.minecraft.server.ServerPlayer; +import fr.pandacube.lib.paper.reflect.wrapper.paper.PaperAdventure; +import fr.pandacube.lib.reflect.wrapper.ReflectWrapper; +import fr.pandacube.lib.util.Log; +import net.kyori.adventure.text.Component; +import org.bukkit.Bukkit; +import org.bukkit.NamespacedKey; +import org.bukkit.World; +import org.bukkit.command.Command; +import org.bukkit.command.CommandMap; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.command.PluginCommand; +import org.bukkit.command.defaults.BukkitCommand; +import org.bukkit.entity.Entity; +import org.bukkit.entity.Player; +import org.bukkit.event.EventHandler; +import org.bukkit.event.Listener; +import org.bukkit.event.player.PlayerCommandSendEvent; +import org.bukkit.event.server.ServerLoadEvent; +import org.bukkit.plugin.Plugin; +import org.bukkit.util.BlockVector; +import org.bukkit.util.Vector; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; + +public abstract class PaperBrigadierCommand extends BrigadierCommand implements Listener { + + protected static final Commands vanillaCommandDispatcher; + private static final CommandDispatcher nmsDispatcher; + + static { + vanillaCommandDispatcher = ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class) + .getServer() + .vanillaCommandDispatcher(); + nmsDispatcher = vanillaCommandDispatcher.dispatcher(); + } + + + public static CommandDispatcher getNMSDispatcher() { + return nmsDispatcher; + } + + protected static RootCommandNode getRootNode() { + return nmsDispatcher.getRoot(); + } + + protected Plugin plugin; + + protected LiteralCommandNode commandNode; + + private Set allAliases; + + public PaperBrigadierCommand(Plugin pl) { + plugin = pl; + commandNode = buildCommand().build(); + postBuildCommand(commandNode); + register(); + Bukkit.getPluginManager().registerEvents(this, plugin); + } + + + + + private void register() { + + String[] aliases = getAliases(); + if (aliases == null) + aliases = new String[0]; + + String pluginName = plugin.getName().toLowerCase(); + + allAliases = new HashSet<>(); + registerNode(commandNode, false); + registerAlias(pluginName + ":" + commandNode.getLiteral(), true); + + for (String alias : aliases) { + registerAlias(alias, false); + registerAlias(pluginName + ":" + alias, true); + } + } + + + private void registerAlias(String alias, boolean prefixed) { + LiteralCommandNode node = literal(alias) + .requires(commandNode.getRequirement()) + .executes(commandNode.getCommand()) + .redirect(commandNode) + .build(); + registerNode(node, prefixed); + } + + + private void registerNode(LiteralCommandNode node, boolean prefixed) { + RootCommandNode root = getRootNode(); + String name = node.getLiteral(); + //boolean isAlias = node.getRedirect() == commandNode; + boolean forceRegistration = true;//prefixed || !isAlias; + + // nmsDispatcher integration and conflit resolution + boolean nmsRegister = false, nmsRegistered = false; + CommandNode nmsConflited = root.getChild(name); + if (nmsConflited != null) { + + if (isFromThisCommand(nmsConflited)) { + // this command is already registered in NMS + // don’t need to register again + nmsRegistered = true; + } + else if (forceRegistration) { + nmsRegister = true; + Log.info("Overwriting Brigadier command /" + name); + } + else { + Log.severe("/" + name + " already in NMS Brigadier instance." + + " Wont replace it because registration is not forced."); + } + } + else { + nmsRegister = true; + } + + if (nmsRegister) { + @SuppressWarnings("unchecked") + var rCommandNode = ReflectWrapper.wrapTyped(root, fr.pandacube.lib.paper.reflect.wrapper.brigadier.CommandNode.class); + rCommandNode.removeCommand(name); + root.addChild(node); + nmsRegistered = true; + } + + if (!nmsRegistered) { + return; + } + + allAliases.add(name); + + // bukkit dispatcher conflict resolution + boolean bukkitRegister = false; + CommandMap bukkitCmdMap = Bukkit.getCommandMap(); + Command bukkitConflicted = bukkitCmdMap.getCommand(name); + if (bukkitConflicted != null) { + if (isFromThisCommand(bukkitConflicted)) { + // nothing to do, already good + } + else if (forceRegistration) { + bukkitRegister = true; + Log.info("Overwriting Bukkit command /" + name + + " (" + getCommandIdentity(bukkitConflicted) + ")"); + } + else { + Log.severe("/" + name + " already in Bukkit" + + " dispatcher (" + getCommandIdentity(bukkitConflicted) + + "). Wont replace it because registration is not forced."); + } + } + else { + bukkitRegister = true; + } + + if (bukkitRegister) { + bukkitCmdMap.getKnownCommands().remove(name.toLowerCase()); + if (bukkitConflicted != null) + bukkitConflicted.unregister(bukkitCmdMap); + + Command newCommand = new VanillaCommandWrapper(vanillaCommandDispatcher, node).__getRuntimeInstance(); + bukkitCmdMap.getKnownCommands().put(name.toLowerCase(), newCommand); + newCommand.register(bukkitCmdMap); + } + + } + + private boolean isFromThisCommand(CommandNode node) { + return node == commandNode || node.getRedirect() == commandNode; + } + + private boolean isFromThisCommand(Command bukkitCmd) { + if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { + return isFromThisCommand(ReflectWrapper.wrapTyped((BukkitCommand) bukkitCmd, VanillaCommandWrapper.class).vanillaCommand()); + } + return false; + } + + protected static String getCommandIdentity(Command bukkitCmd) { + if (bukkitCmd instanceof PluginCommand cmd) { + return "Bukkit command: /" + cmd.getName() + " from plugin " + cmd.getPlugin().getName(); + } + else if (VanillaCommandWrapper.REFLECT.get().isInstance(bukkitCmd)) { + return "Vanilla command: /" + bukkitCmd.getName(); + } + else + return bukkitCmd.getClass().getName() + ": /" + bukkitCmd.getName(); + } + + + + + @EventHandler + public void onPlayerCommandSend(PlayerCommandSendEvent event) { + event.getCommands().removeAll(allAliases.stream().map(s -> "minecraft:" + s).toList()); + } + + + @EventHandler + public void onServerLoad(ServerLoadEvent event) { + register(); + } + + + + /** + * The permission that should be tested instead of "minecraft.command.cmdName". + */ + protected abstract String getTargetPermission(); + + + + + + + + + + + + + + + public boolean isConsole(BukkitBrigadierCommandSource wrapper) { + return isConsole(getCommandSender(wrapper)); + } + public boolean isPlayer(BukkitBrigadierCommandSource wrapper) { + return isPlayer(getCommandSender(wrapper)); + } + public Predicate hasPermission(String permission) { + return wrapper -> getCommandSender(wrapper).hasPermission(permission); + } + + + public boolean isConsole(CommandSender sender) { + return sender instanceof ConsoleCommandSender; + } + public boolean isPlayer(CommandSender sender) { + return sender instanceof Player; + } + + + + + public static CommandSender getCommandSender(CommandContext context) { + return getCommandSender(context.getSource()); + } + public static CommandSender getCommandSender(BukkitBrigadierCommandSource wrapper) { + return wrapper.getBukkitSender(); + } + public static BukkitBrigadierCommandSource getBrigadierCommandSource(CommandSender sender) { + return VanillaCommandWrapper.getListener(sender); + } + + + + + + + + + + + + + + + + public static final SuggestionsSupplier TAB_WORLDS = (s, ti, token, a) -> SuggestionsSupplier.collectFilteredStream(Bukkit.getWorlds().stream().map(World::getName), token); + + + + protected SuggestionProvider wrapSuggestions(SuggestionsSupplier suggestions) { + return wrapSuggestions(suggestions, PaperBrigadierCommand::getCommandSender); + } + + + + + protected static com.mojang.brigadier.Command wrapCommand(com.mojang.brigadier.Command cmd) { + return context -> { + try { + return cmd.run(context); + } catch(CommandSyntaxException e) { + throw e; + } catch (Throwable t) { + Log.severe(t); + getCommandSender(context).sendMessage(Chat.failureText("Error while using the command: " + t)); + return 0; + } + }; + } + + + + + + + + + /* + * Minecraft argument type + */ + + + public static ArgumentType argumentMinecraftEntity(boolean singleTarget, boolean playersOnly) { + if (playersOnly) { + return singleTarget ? EntityArgument.player() : EntityArgument.players(); + } + else { + return singleTarget ? EntityArgument.entity() : EntityArgument.entities(); + } + } + + public List tryGetMinecraftEntityArgument(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + List nmsEntityList = es.findEntities(context.getSource()); + List entityList = new ArrayList<>(nmsEntityList.size()); + for (fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity : nmsEntityList) { + entityList.add(nmsEntity.getBukkitEntity()); + } + return entityList; + } + + public List tryGetMinecraftEntityArgumentPlayers(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + List nmsPlayerList = es.findPlayers(context.getSource()); + List playerList = new ArrayList<>(nmsPlayerList.size()); + for (ServerPlayer nmsPlayer : nmsPlayerList) { + playerList.add(nmsPlayer.getBukkitEntity()); + } + return playerList; + } + + public Entity tryGetMinecraftEntityArgumentOneEntity(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity nmsEntity = es.findSingleEntity(context.getSource()); + return nmsEntity == null ? null : nmsEntity.getBukkitEntity(); + } + + public Player tryGetMinecraftEntityArgumentOnePlayer(CommandContext context, String argument) { + EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class); + if (es == null) + return null; + ServerPlayer nmsPlayer = es.findSinglePlayer(context.getSource()); + return nmsPlayer == null ? null : nmsPlayer.getBukkitEntity(); + } + + + + + public static ArgumentType argumentMinecraftGameProfile() { + return GameProfileArgument.gameProfile(); + } + + + + public static ArgumentType argumentMinecraftResourceLocation() { + return ResourceLocationArgument.id(); + } + public NamespacedKey tryGetMinecraftResourceLocationArgument(CommandContext context, String argument, NamespacedKey deflt) { + return tryGetArgument(context, argument, ResourceLocation.MAPPING.runtimeClass(), + nmsKey -> CraftNamespacedKey.fromMinecraft(ReflectWrapper.wrap(nmsKey, ResourceLocation.class)), + deflt); + } + + + + + public static ArgumentType argumentMinecraftBlockPosition() { + return BlockPosArgument.blockPos(); + } + public BlockVector tryGetMinecraftBlockPositionArgument(CommandContext context, String argument) { + Coordinates coord = ReflectWrapper.wrap(tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass()), Coordinates.class); + if (coord == null) + return null; + BlockPos bp = coord.getBlockPos(context.getSource()); + return new BlockVector(bp.getX(), bp.getY(), bp.getZ()); + + } + + + + + public static ArgumentType argumentMinecraftVec3() { + return Vec3Argument.vec3(true); + } + public Vector tryGetMinecraftVec3Argument(CommandContext context, String argument) { + Coordinates coord = ReflectWrapper.wrap(tryGetArgument(context, argument, Coordinates.MAPPING.runtimeClass()), Coordinates.class); + return coord == null ? null : CraftVector.toBukkit(coord.getPosition(context.getSource())); + + } + + + + + public static ArgumentType argumentMinecraftChatComponent() { + return ComponentArgument.textComponent(); + } + public Component tryGetMinecraftChatComponentArgument(CommandContext context, String argument) { + var nmsComponent = ReflectWrapper.wrap( + tryGetArgument(context, argument, fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.MAPPING.runtimeClass()), + fr.pandacube.lib.paper.reflect.wrapper.minecraft.network.chat.Component.class + ); + return nmsComponent == null ? null : PaperAdventure.asAdventure(nmsComponent); + } + + + + + +} diff --git a/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java b/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java index 77dc687..f0be8bb 100644 --- a/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java +++ b/pandalib-paper-reflect/src/main/java/fr/pandacube/lib/paper/reflect/PandalibPaperReflect.java @@ -66,8 +66,14 @@ import fr.pandacube.lib.paper.reflect.wrapper.paper.configuration.WorldConfigura import static fr.pandacube.lib.reflect.wrapper.WrapperRegistry.initWrapper; +/** + * Initializer for all the reflect tools in {@code pandalib-paper-reflect} module. + */ public class PandalibPaperReflect { + /** + * Initializes the reflect tools in {@code pandalib-paper-reflect} module. + */ public static void init() { NMSReflect.init(); initWrapperClasses(); diff --git a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java index 89c4114..3659ab8 100644 --- a/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java +++ b/pandalib-paper/src/main/java/fr/pandacube/lib/paper/util/Skull.java @@ -131,7 +131,7 @@ public enum Skull { /** * Return a skull that has a custom texture specified by url. * - * @param url skin url + * @param url the skin full url * @return itemstack */ public static ItemStack getFromSkinURL(String url, Chat name, List lore) { diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/WrapperRegistry.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/WrapperRegistry.java index bfd5c21..f812d5b 100644 --- a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/WrapperRegistry.java +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/WrapperRegistry.java @@ -8,6 +8,9 @@ import java.lang.reflect.Modifier; import java.util.HashMap; import java.util.Map; +/** + * Class in which each wrapper classes must be registered, using {@link #initWrapper(Class, Class)}. + */ public class WrapperRegistry { /* package */ static Class getWrapperOfRuntimeClass(Class runtime) { @@ -32,12 +35,11 @@ public class WrapperRegistry { private static final Map, RegistryEntry> WRAPPER_DATA_BY_WRAPPER_CLASS = new HashMap<>(); - - - - - - + /** + * Registers a new wrapper class. + * @param wrapper the wrapper class. + * @param runtime the runtime class, that will be accessed reflectively by the wrapper class. + */ public static void initWrapper(Class wrapper, Class runtime) { Class concreteWrapper = wrapper; ReflectConstructor objectWrapperConstructor; diff --git a/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/package-info.java b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/package-info.java new file mode 100644 index 0000000..43dee25 --- /dev/null +++ b/pandalib-reflect/src/main/java/fr/pandacube/lib/reflect/wrapper/package-info.java @@ -0,0 +1,9 @@ +/** + * Set of class allowing applications to implement almost transparent reflection classes. + * The goal it to implement the class, methods and fields that the application have only access through reflection, and + * reflection call when these implementation are called. + * Each of those reflection classes must extend {@link fr.pandacube.lib.reflect.wrapper.ReflectWrapper} (or, if it’s + * an interface, must extend {@link fr.pandacube.lib.reflect.wrapper.ReflectWrapperI}). The implemented class wraps + * the reflected object and redirects the method calls to them using reflection. + */ +package fr.pandacube.lib.reflect.wrapper; \ No newline at end of file diff --git a/pom.xml b/pom.xml index 0a3bf3a..ea27d43 100644 --- a/pom.xml +++ b/pom.xml @@ -62,6 +62,7 @@ pandalib-bungee + pandalib-bungee-commands pandalib-bungee-permissions pandalib-bungee-players pandalib-chat @@ -72,6 +73,7 @@ pandalib-net pandalib-netapi pandalib-paper + pandalib-paper-commands pandalib-paper-permissions pandalib-paper-players pandalib-paper-reflect