Added new Brigadier commands related APIs for bungee/paper/cli + a few javadoc
This commit is contained in:
parent
2f141d5f84
commit
a885c224a6
48
pandalib-bungee-commands/pom.xml
Normal file
48
pandalib-bungee-commands/pom.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>pandalib-parent</artifactId>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>pandalib-bungee-commands</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>bungeecord-repo</id>
|
||||||
|
<url>https://oss.sonatype.org/content/repositories/snapshots</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-chat</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-commands</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.md-5</groupId>
|
||||||
|
<artifactId>bungeecord-api</artifactId>
|
||||||
|
<version>${bungeecord.version}</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -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<CommandSender> {
|
||||||
|
|
||||||
|
protected BungeeBrigadierDispatcher dispatcher;
|
||||||
|
|
||||||
|
public BungeeBrigadierCommand(BungeeBrigadierDispatcher d) {
|
||||||
|
if (d == null) {
|
||||||
|
throw new IllegalStateException("BungeeBrigadierDispatcher not provided.");
|
||||||
|
}
|
||||||
|
dispatcher = d;
|
||||||
|
|
||||||
|
LiteralCommandNode<CommandSender> 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<String> 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<CommandSender> hasPermission(String permission) {
|
||||||
|
return sender -> sender.hasPermission(permission);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected SuggestionProvider<CommandSender> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) {
|
||||||
|
return wrapSuggestions(suggestions, Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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<CommandSender> 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()));
|
||||||
|
}
|
||||||
|
}
|
@ -32,12 +32,16 @@
|
|||||||
<artifactId>pandalib-core</artifactId>
|
<artifactId>pandalib-core</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
<artifactId>pandalib-reflect</artifactId>
|
<artifactId>pandalib-reflect</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-commands</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>net.md-5</groupId>
|
<groupId>net.md-5</groupId>
|
||||||
@ -50,12 +54,6 @@
|
|||||||
<version>${bungeecord.version}</version>
|
<version>${bungeecord.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>fr.pandacube.lib</groupId>
|
|
||||||
<artifactId>pandalib-commands</artifactId>
|
|
||||||
<version>${project.version}</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
</project>
|
</project>
|
||||||
|
@ -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<Object> builder = buildCommand();
|
|
||||||
String[] aliases = getAliases();
|
|
||||||
if (aliases == null)
|
|
||||||
aliases = new String[0];
|
|
||||||
|
|
||||||
LiteralCommandNode<Object> 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<Object> buildCommand();
|
|
||||||
|
|
||||||
protected String[] getAliases() {
|
|
||||||
return new String[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public static LiteralArgumentBuilder<Object> literal(String name) {
|
|
||||||
return LiteralArgumentBuilder.literal(name);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> RequiredArgumentBuilder<Object, T> argument(String name, ArgumentType<T> type) {
|
|
||||||
return RequiredArgumentBuilder.argument(name, type);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static boolean isLiteralParsed(CommandContext<Object> context, String literal) {
|
|
||||||
for (ParsedCommandNode<Object> node : context.getNodes()) {
|
|
||||||
if (!(node.getNode() instanceof LiteralCommandNode))
|
|
||||||
continue;
|
|
||||||
if (((LiteralCommandNode<Object>)node.getNode()).getLiteral().equals(literal))
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T tryGetArgument(CommandContext<Object> context, String argument, Class<T> type) {
|
|
||||||
return tryGetArgument(context, argument, type, null);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static <T> T tryGetArgument(CommandContext<Object> context, String argument, Class<T> type, T deflt) {
|
|
||||||
try {
|
|
||||||
return context.getArgument(argument, type);
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
return deflt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
protected static SuggestionProvider<Object> wrapSuggestions(SuggestionsSupplier<Object> 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<String> 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<Suggestions> completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) {
|
|
||||||
return CompletableFuture.completedFuture(
|
|
||||||
BrigadierDispatcher.createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) {
|
|
||||||
try {
|
|
||||||
return (List<Suggestion>) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder);
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
@ -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<Object> dispatcher;
|
|
||||||
|
|
||||||
private final Object sender = new Object();
|
|
||||||
|
|
||||||
public BrigadierDispatcher() {
|
|
||||||
dispatcher = new CommandDispatcher<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/* package */ LiteralCommandNode<Object> register(LiteralArgumentBuilder<Object> node) {
|
|
||||||
return dispatcher.register(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public int execute(String commandWithoutSlash) {
|
|
||||||
ParseResults<Object> 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<CharSequence> 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<Object> parsed = dispatcher.parse(buffer, sender);
|
|
||||||
try {
|
|
||||||
CompletableFuture<Suggestions> 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<Suggestions> buildSuggestionBrigadier(ParseResults<Object> parsed) {
|
|
||||||
int cursor = parsed.getReader().getTotalLength();
|
|
||||||
final CommandContextBuilder<Object> context = parsed.getContext();
|
|
||||||
|
|
||||||
final SuggestionContext<Object> nodeBeforeCursor = context.findSuggestionContext(cursor);
|
|
||||||
final CommandNode<Object> 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<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
|
|
||||||
int i = 0;
|
|
||||||
for (final CommandNode<Object> node : parent.getChildren()) {
|
|
||||||
CompletableFuture<Suggestions> future = Suggestions.empty();
|
|
||||||
try {
|
|
||||||
future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start));
|
|
||||||
} catch (final CommandSyntaxException ignored) {
|
|
||||||
}
|
|
||||||
futures[i++] = future;
|
|
||||||
}
|
|
||||||
|
|
||||||
final CompletableFuture<Suggestions> result = new CompletableFuture<>();
|
|
||||||
CompletableFuture.allOf(futures).thenRun(() -> {
|
|
||||||
final List<Suggestions> suggestions = new ArrayList<>();
|
|
||||||
for (final CompletableFuture<Suggestions> 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> suggestions) {
|
|
||||||
if (suggestions.isEmpty()) {
|
|
||||||
return new Suggestions(StringRange.at(0), new ArrayList<>(0));
|
|
||||||
} else if (suggestions.size() == 1) {
|
|
||||||
return suggestions.iterator().next();
|
|
||||||
}
|
|
||||||
|
|
||||||
final List<Suggestion> 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<Suggestion> 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<Suggestion> texts = new ArrayList<>(suggestions.size());
|
|
||||||
for (final Suggestion suggestion : suggestions) {
|
|
||||||
texts.add(suggestion.expand(command, range));
|
|
||||||
}
|
|
||||||
return new Suggestions(range, texts);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
@ -3,6 +3,8 @@ package fr.pandacube.lib.cli;
|
|||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
|
import fr.pandacube.lib.cli.commands.CLIBrigadierDispatcher;
|
||||||
|
import fr.pandacube.lib.cli.log.CLILogger;
|
||||||
import jline.console.ConsoleReader;
|
import jline.console.ConsoleReader;
|
||||||
import org.fusesource.jansi.AnsiConsole;
|
import org.fusesource.jansi.AnsiConsole;
|
||||||
|
|
||||||
@ -47,7 +49,7 @@ public class CLI {
|
|||||||
reader = new ConsoleReader();
|
reader = new ConsoleReader();
|
||||||
reader.setBellEnabled(false);
|
reader.setBellEnabled(false);
|
||||||
reader.setPrompt("\r>");
|
reader.setPrompt("\r>");
|
||||||
reader.addCompleter(BrigadierDispatcher.instance);
|
reader.addCompleter(CLIBrigadierDispatcher.instance);
|
||||||
|
|
||||||
// configuration du formatteur pour le logger
|
// configuration du formatteur pour le logger
|
||||||
System.setProperty("net.md_5.bungee.log-date-format", "yyyy-MM-dd HH:mm:ss");
|
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(""))
|
if (line.trim().equals(""))
|
||||||
continue;
|
continue;
|
||||||
String cmdLine = line;
|
String cmdLine = line;
|
||||||
new Thread(() -> BrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start();
|
new Thread(() -> CLIBrigadierDispatcher.instance.execute(cmdLine), "CLICmdThread #"+(i++)).start();
|
||||||
|
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
@ -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<Object> {
|
||||||
|
|
||||||
|
public CLIBrigadierCommand() {
|
||||||
|
LiteralCommandNode<Object> 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<Object> buildCommand();
|
||||||
|
|
||||||
|
protected String[] getAliases() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public boolean isPlayer(Object sender) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isConsole(Object sender) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Predicate<Object> hasPermission(String permission) {
|
||||||
|
return sender -> true;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected SuggestionProvider<Object> wrapSuggestions(SuggestionsSupplier<Object> suggestions) {
|
||||||
|
return wrapSuggestions(suggestions, Function.identity());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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<Object> 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<CharSequence> 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());
|
||||||
|
}
|
||||||
|
}
|
@ -1,10 +1,12 @@
|
|||||||
package fr.pandacube.lib.cli;
|
package fr.pandacube.lib.cli.log;
|
||||||
|
|
||||||
import java.io.PrintStream;
|
import java.io.PrintStream;
|
||||||
import java.util.logging.Handler;
|
import java.util.logging.Handler;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
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.ColouredWriter;
|
||||||
import net.md_5.bungee.log.ConciseFormatter;
|
import net.md_5.bungee.log.ConciseFormatter;
|
||||||
import net.md_5.bungee.log.LoggingOutputStream;
|
import net.md_5.bungee.log.LoggingOutputStream;
|
||||||
@ -13,7 +15,7 @@ public class CLILogger {
|
|||||||
|
|
||||||
private static Logger logger = null;
|
private static Logger logger = null;
|
||||||
|
|
||||||
/* package */ static synchronized Logger getLogger(CLI cli) {
|
public static synchronized Logger getLogger(CLI cli) {
|
||||||
if (logger == null) {
|
if (logger == null) {
|
||||||
logger = Logger.getGlobal();
|
logger = Logger.getGlobal();
|
||||||
logger.setLevel(Level.ALL);
|
logger.setLevel(Level.ALL);
|
||||||
@ -30,7 +32,8 @@ public class CLILogger {
|
|||||||
|
|
||||||
System.setErr(new PrintStream(new LoggingOutputStream(logger, Level.SEVERE), true));
|
System.setErr(new PrintStream(new LoggingOutputStream(logger, Level.SEVERE), true));
|
||||||
System.setOut(new PrintStream(new LoggingOutputStream(logger, Level.INFO), true));
|
System.setOut(new PrintStream(new LoggingOutputStream(logger, Level.INFO), true));
|
||||||
|
|
||||||
|
Log.setLogger(logger);
|
||||||
}
|
}
|
||||||
return logger;
|
return logger;
|
||||||
}
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package fr.pandacube.lib.cli;
|
package fr.pandacube.lib.cli.log;
|
||||||
|
|
||||||
import java.io.BufferedWriter;
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
@ -31,6 +31,16 @@
|
|||||||
<artifactId>pandalib-util</artifactId>
|
<artifactId>pandalib-util</artifactId>
|
||||||
<version>${project.version}</version>
|
<version>${project.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-chat</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-reflect</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mojang</groupId>
|
<groupId>com.mojang</groupId>
|
||||||
|
@ -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 <S> the command source (or command sender) type.
|
||||||
|
*/
|
||||||
|
public abstract class BrigadierCommand<S> {
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected abstract LiteralArgumentBuilder<S> 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<S> commandNode) {
|
||||||
|
// default implementation does nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getAliases() {
|
||||||
|
return new String[0];
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public LiteralArgumentBuilder<S> literal(String name) {
|
||||||
|
return LiteralArgumentBuilder.literal(name);
|
||||||
|
}
|
||||||
|
public <T> RequiredArgumentBuilder<S, T> argument(String name, ArgumentType<T> type) {
|
||||||
|
return RequiredArgumentBuilder.argument(name, type);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public abstract boolean isPlayer(S sender);
|
||||||
|
|
||||||
|
public abstract boolean isConsole(S sender);
|
||||||
|
|
||||||
|
public Predicate<S> isPlayer() {
|
||||||
|
return this::isPlayer;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Predicate<S> isConsole() {
|
||||||
|
return this::isConsole;
|
||||||
|
}
|
||||||
|
|
||||||
|
public abstract Predicate<S> 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> T tryGetArgument(CommandContext<?> context, String argument, Class<T> type) {
|
||||||
|
return tryGetArgument(context, argument, type, Function.identity(), null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <ST, T> T tryGetArgument(CommandContext<?> context, String argument, Class<ST> sourceType, Function<ST, T> transformIfFound) {
|
||||||
|
return tryGetArgument(context, argument, sourceType, transformIfFound, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <T> T tryGetArgument(CommandContext<?> context, String argument, Class<T> type, T deflt) {
|
||||||
|
return tryGetArgument(context, argument, type, Function.identity(), deflt);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static <ST, T> T tryGetArgument(CommandContext<?> context, String argument, Class<ST> sourceType, Function<ST, T> 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 <AS> SuggestionProvider<S> wrapSuggestions(SuggestionsSupplier<AS> suggestions, Function<S, AS> 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);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -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<S> {
|
||||||
|
|
||||||
|
|
||||||
|
private final CommandDispatcher<S> dispatcher = new CommandDispatcher<>();
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public void register(LiteralCommandNode<S> node) {
|
||||||
|
dispatcher.getRoot().addChild(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public CommandDispatcher<S> getDispatcher() {
|
||||||
|
return dispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public int execute(S sender, String commandWithoutSlash) {
|
||||||
|
ParseResults<S> 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<S> parsed = dispatcher.parse(buffer, sender);
|
||||||
|
try {
|
||||||
|
CompletableFuture<Suggestions> 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);
|
||||||
|
|
||||||
|
}
|
@ -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 <S> CompletableFuture<Suggestions> buildSuggestionBrigadier(ParseResults<S> parsed) {
|
||||||
|
int cursor = parsed.getReader().getTotalLength();
|
||||||
|
final CommandContextBuilder<S> context = parsed.getContext();
|
||||||
|
|
||||||
|
final SuggestionContext<S> nodeBeforeCursor = context.findSuggestionContext(cursor);
|
||||||
|
final CommandNode<S> 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<Suggestions>[] futures = new CompletableFuture[parent.getChildren().size()];
|
||||||
|
int i = 0;
|
||||||
|
for (final CommandNode<S> node : parent.getChildren()) {
|
||||||
|
CompletableFuture<Suggestions> future = Suggestions.empty();
|
||||||
|
try {
|
||||||
|
future = node.listSuggestions(context.build(truncatedInput), new SuggestionsBuilder(truncatedInput, start));
|
||||||
|
} catch (final CommandSyntaxException ignored) {
|
||||||
|
}
|
||||||
|
futures[i++] = future;
|
||||||
|
}
|
||||||
|
|
||||||
|
final CompletableFuture<Suggestions> result = new CompletableFuture<>();
|
||||||
|
CompletableFuture.allOf(futures).thenRun(() -> {
|
||||||
|
final List<Suggestions> suggestions = new ArrayList<>();
|
||||||
|
for (final CompletableFuture<Suggestions> 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> suggestions) {
|
||||||
|
if (suggestions.isEmpty()) {
|
||||||
|
return new Suggestions(StringRange.at(0), new ArrayList<>(0));
|
||||||
|
} else if (suggestions.size() == 1) {
|
||||||
|
return suggestions.iterator().next();
|
||||||
|
}
|
||||||
|
|
||||||
|
final List<Suggestion> 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<Suggestion> 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<Suggestion> texts = new ArrayList<>(suggestions.size());
|
||||||
|
for (final Suggestion suggestion : suggestions) {
|
||||||
|
texts.add(suggestion.expand(command, range));
|
||||||
|
}
|
||||||
|
return new Suggestions(range, texts);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static CompletableFuture<Suggestions> completableFutureSuggestionsKeepsOriginalOrdering(SuggestionsBuilder builder) {
|
||||||
|
return CompletableFuture.completedFuture(
|
||||||
|
createSuggestionsOriginalOrdering(builder.getInput(), getSuggestionsFromSuggestionsBuilder(builder))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static List<Suggestion> getSuggestionsFromSuggestionsBuilder(SuggestionsBuilder builder) {
|
||||||
|
try {
|
||||||
|
return (List<Suggestion>) Reflect.ofClass(SuggestionsBuilder.class).field("result").getValue(builder);
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
72
pandalib-paper-commands/pom.xml
Normal file
72
pandalib-paper-commands/pom.xml
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<parent>
|
||||||
|
<artifactId>pandalib-parent</artifactId>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<version>1.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<artifactId>pandalib-paper-commands</artifactId>
|
||||||
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
<repositories>
|
||||||
|
<repository>
|
||||||
|
<id>papermc</id>
|
||||||
|
<url>https://papermc.io/repo/repository/maven-public/</url>
|
||||||
|
</repository>
|
||||||
|
</repositories>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-chat</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<exclusions>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-api</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
<exclusion>
|
||||||
|
<groupId>net.kyori</groupId>
|
||||||
|
<artifactId>adventure-text-serializer-plain</artifactId>
|
||||||
|
</exclusion>
|
||||||
|
</exclusions>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-commands</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-paper-reflect</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>fr.pandacube.lib</groupId>
|
||||||
|
<artifactId>pandalib-util</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Paper -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-api</artifactId>
|
||||||
|
<version>${paper.version}-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.papermc.paper</groupId>
|
||||||
|
<artifactId>paper-mojangapi</artifactId>
|
||||||
|
<version>${paper.version}-SNAPSHOT</version>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
</project>
|
@ -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<BukkitBrigadierCommandSource> implements Listener {
|
||||||
|
|
||||||
|
protected static final Commands vanillaCommandDispatcher;
|
||||||
|
private static final CommandDispatcher<BukkitBrigadierCommandSource> nmsDispatcher;
|
||||||
|
|
||||||
|
static {
|
||||||
|
vanillaCommandDispatcher = ReflectWrapper.wrapTyped(Bukkit.getServer(), CraftServer.class)
|
||||||
|
.getServer()
|
||||||
|
.vanillaCommandDispatcher();
|
||||||
|
nmsDispatcher = vanillaCommandDispatcher.dispatcher();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
public static CommandDispatcher<BukkitBrigadierCommandSource> getNMSDispatcher() {
|
||||||
|
return nmsDispatcher;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static RootCommandNode<BukkitBrigadierCommandSource> getRootNode() {
|
||||||
|
return nmsDispatcher.getRoot();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Plugin plugin;
|
||||||
|
|
||||||
|
protected LiteralCommandNode<BukkitBrigadierCommandSource> commandNode;
|
||||||
|
|
||||||
|
private Set<String> 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<BukkitBrigadierCommandSource> node = literal(alias)
|
||||||
|
.requires(commandNode.getRequirement())
|
||||||
|
.executes(commandNode.getCommand())
|
||||||
|
.redirect(commandNode)
|
||||||
|
.build();
|
||||||
|
registerNode(node, prefixed);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private void registerNode(LiteralCommandNode<BukkitBrigadierCommandSource> node, boolean prefixed) {
|
||||||
|
RootCommandNode<BukkitBrigadierCommandSource> 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<BukkitBrigadierCommandSource> 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<BukkitBrigadierCommandSource> 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<BukkitBrigadierCommandSource> 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<BukkitBrigadierCommandSource> 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<CommandSender> TAB_WORLDS = (s, ti, token, a) -> SuggestionsSupplier.collectFilteredStream(Bukkit.getWorlds().stream().map(World::getName), token);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected SuggestionProvider<BukkitBrigadierCommandSource> wrapSuggestions(SuggestionsSupplier<CommandSender> suggestions) {
|
||||||
|
return wrapSuggestions(suggestions, PaperBrigadierCommand::getCommandSender);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
protected static com.mojang.brigadier.Command<BukkitBrigadierCommandSource> wrapCommand(com.mojang.brigadier.Command<BukkitBrigadierCommandSource> 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<Object> argumentMinecraftEntity(boolean singleTarget, boolean playersOnly) {
|
||||||
|
if (playersOnly) {
|
||||||
|
return singleTarget ? EntityArgument.player() : EntityArgument.players();
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
return singleTarget ? EntityArgument.entity() : EntityArgument.entities();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<Entity> tryGetMinecraftEntityArgument(CommandContext<BukkitBrigadierCommandSource> context, String argument) {
|
||||||
|
EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class);
|
||||||
|
if (es == null)
|
||||||
|
return null;
|
||||||
|
List<fr.pandacube.lib.paper.reflect.wrapper.minecraft.world.Entity> nmsEntityList = es.findEntities(context.getSource());
|
||||||
|
List<Entity> 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<Player> tryGetMinecraftEntityArgumentPlayers(CommandContext<BukkitBrigadierCommandSource> context, String argument) {
|
||||||
|
EntitySelector es = ReflectWrapper.wrap(tryGetArgument(context, argument, EntitySelector.MAPPING.runtimeClass()), EntitySelector.class);
|
||||||
|
if (es == null)
|
||||||
|
return null;
|
||||||
|
List<ServerPlayer> nmsPlayerList = es.findPlayers(context.getSource());
|
||||||
|
List<Player> playerList = new ArrayList<>(nmsPlayerList.size());
|
||||||
|
for (ServerPlayer nmsPlayer : nmsPlayerList) {
|
||||||
|
playerList.add(nmsPlayer.getBukkitEntity());
|
||||||
|
}
|
||||||
|
return playerList;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Entity tryGetMinecraftEntityArgumentOneEntity(CommandContext<BukkitBrigadierCommandSource> 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<BukkitBrigadierCommandSource> 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<Object> argumentMinecraftGameProfile() {
|
||||||
|
return GameProfileArgument.gameProfile();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static ArgumentType<Object> argumentMinecraftResourceLocation() {
|
||||||
|
return ResourceLocationArgument.id();
|
||||||
|
}
|
||||||
|
public NamespacedKey tryGetMinecraftResourceLocationArgument(CommandContext<BukkitBrigadierCommandSource> context, String argument, NamespacedKey deflt) {
|
||||||
|
return tryGetArgument(context, argument, ResourceLocation.MAPPING.runtimeClass(),
|
||||||
|
nmsKey -> CraftNamespacedKey.fromMinecraft(ReflectWrapper.wrap(nmsKey, ResourceLocation.class)),
|
||||||
|
deflt);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
public static ArgumentType<Object> argumentMinecraftBlockPosition() {
|
||||||
|
return BlockPosArgument.blockPos();
|
||||||
|
}
|
||||||
|
public BlockVector tryGetMinecraftBlockPositionArgument(CommandContext<BukkitBrigadierCommandSource> 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<Object> argumentMinecraftVec3() {
|
||||||
|
return Vec3Argument.vec3(true);
|
||||||
|
}
|
||||||
|
public Vector tryGetMinecraftVec3Argument(CommandContext<BukkitBrigadierCommandSource> 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<Object> argumentMinecraftChatComponent() {
|
||||||
|
return ComponentArgument.textComponent();
|
||||||
|
}
|
||||||
|
public Component tryGetMinecraftChatComponentArgument(CommandContext<BukkitBrigadierCommandSource> 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);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -66,8 +66,14 @@ import fr.pandacube.lib.paper.reflect.wrapper.paper.configuration.WorldConfigura
|
|||||||
|
|
||||||
import static fr.pandacube.lib.reflect.wrapper.WrapperRegistry.initWrapper;
|
import static fr.pandacube.lib.reflect.wrapper.WrapperRegistry.initWrapper;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializer for all the reflect tools in {@code pandalib-paper-reflect} module.
|
||||||
|
*/
|
||||||
public class PandalibPaperReflect {
|
public class PandalibPaperReflect {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initializes the reflect tools in {@code pandalib-paper-reflect} module.
|
||||||
|
*/
|
||||||
public static void init() {
|
public static void init() {
|
||||||
NMSReflect.init();
|
NMSReflect.init();
|
||||||
initWrapperClasses();
|
initWrapperClasses();
|
||||||
|
@ -131,7 +131,7 @@ public enum Skull {
|
|||||||
/**
|
/**
|
||||||
* Return a skull that has a custom texture specified by url.
|
* Return a skull that has a custom texture specified by url.
|
||||||
*
|
*
|
||||||
* @param url skin url
|
* @param url the skin full url
|
||||||
* @return itemstack
|
* @return itemstack
|
||||||
*/
|
*/
|
||||||
public static ItemStack getFromSkinURL(String url, Chat name, List<Chat> lore) {
|
public static ItemStack getFromSkinURL(String url, Chat name, List<Chat> lore) {
|
||||||
|
@ -8,6 +8,9 @@ import java.lang.reflect.Modifier;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class in which each wrapper classes must be registered, using {@link #initWrapper(Class, Class)}.
|
||||||
|
*/
|
||||||
public class WrapperRegistry {
|
public class WrapperRegistry {
|
||||||
|
|
||||||
/* package */ static Class<? extends ReflectWrapperI> getWrapperOfRuntimeClass(Class<?> runtime) {
|
/* package */ static Class<? extends ReflectWrapperI> getWrapperOfRuntimeClass(Class<?> runtime) {
|
||||||
@ -32,12 +35,11 @@ public class WrapperRegistry {
|
|||||||
private static final Map<Class<? extends ReflectWrapperI>, RegistryEntry> WRAPPER_DATA_BY_WRAPPER_CLASS = new HashMap<>();
|
private static final Map<Class<? extends ReflectWrapperI>, 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<? extends ReflectWrapperI> wrapper, Class<?> runtime) {
|
public static void initWrapper(Class<? extends ReflectWrapperI> wrapper, Class<?> runtime) {
|
||||||
Class<? extends ReflectWrapperI> concreteWrapper = wrapper;
|
Class<? extends ReflectWrapperI> concreteWrapper = wrapper;
|
||||||
ReflectConstructor<? extends ReflectWrapperI> objectWrapperConstructor;
|
ReflectConstructor<? extends ReflectWrapperI> objectWrapperConstructor;
|
||||||
|
@ -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;
|
2
pom.xml
2
pom.xml
@ -62,6 +62,7 @@
|
|||||||
|
|
||||||
<modules>
|
<modules>
|
||||||
<module>pandalib-bungee</module>
|
<module>pandalib-bungee</module>
|
||||||
|
<module>pandalib-bungee-commands</module>
|
||||||
<module>pandalib-bungee-permissions</module>
|
<module>pandalib-bungee-permissions</module>
|
||||||
<module>pandalib-bungee-players</module>
|
<module>pandalib-bungee-players</module>
|
||||||
<module>pandalib-chat</module>
|
<module>pandalib-chat</module>
|
||||||
@ -72,6 +73,7 @@
|
|||||||
<module>pandalib-net</module>
|
<module>pandalib-net</module>
|
||||||
<module>pandalib-netapi</module>
|
<module>pandalib-netapi</module>
|
||||||
<module>pandalib-paper</module>
|
<module>pandalib-paper</module>
|
||||||
|
<module>pandalib-paper-commands</module>
|
||||||
<module>pandalib-paper-permissions</module>
|
<module>pandalib-paper-permissions</module>
|
||||||
<module>pandalib-paper-players</module>
|
<module>pandalib-paper-players</module>
|
||||||
<module>pandalib-paper-reflect</module>
|
<module>pandalib-paper-reflect</module>
|
||||||
|
Loading…
Reference in New Issue
Block a user