0.3
This commit is contained in:
parent
f8e79ea869
commit
4f8429e0b1
8 changed files with 298 additions and 151 deletions
|
|
@ -2,10 +2,11 @@ plugins {
|
||||||
id("java")
|
id("java")
|
||||||
id("java-library")
|
id("java-library")
|
||||||
id("maven-publish")
|
id("maven-publish")
|
||||||
|
kotlin("jvm") version "1.7.0"
|
||||||
}
|
}
|
||||||
|
|
||||||
group = "ru.d3st0ny"
|
group = "ru.d3st0ny"
|
||||||
version = "0.1"
|
version = "0.3"
|
||||||
|
|
||||||
repositories {
|
repositories {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
|
|
@ -17,18 +18,17 @@ dependencies {
|
||||||
api("com.mojang:brigadier:1.0.18") {
|
api("com.mojang:brigadier:1.0.18") {
|
||||||
exclude("com.google.guava", "guava")
|
exclude("com.google.guava", "guava")
|
||||||
}
|
}
|
||||||
compileOnly("io.papermc.paper:paper-api:1.19-R0.1-SNAPSHOT")
|
compileOnly("io.papermc.paper:paper-api:1.19.2-R0.1-SNAPSHOT")
|
||||||
|
compileOnly("io.papermc.paper:paper-mojangapi:1.19.2-R0.1-SNAPSHOT")
|
||||||
}
|
}
|
||||||
|
|
||||||
publishing {
|
publishing {
|
||||||
publications {
|
publications.create<MavenPublication>("maven") {
|
||||||
create<MavenPublication>("maven"){
|
|
||||||
artifactId = project.name.toLowerCase()
|
artifactId = project.name.toLowerCase()
|
||||||
groupId = "${project.group}"
|
groupId = "${project.group}"
|
||||||
version = "${project.version}"
|
version = "${project.version}"
|
||||||
from(components["java"])
|
from(components["java"])
|
||||||
}
|
}
|
||||||
}
|
|
||||||
repositories {
|
repositories {
|
||||||
maven {
|
maven {
|
||||||
url = uri("https://gitea.destony.dev/api/packages/Polygon/maven")
|
url = uri("https://gitea.destony.dev/api/packages/Polygon/maven")
|
||||||
|
|
|
||||||
139
src/main/java/ru/d3st0ny/adajency/AbstractAdajency.java
Normal file
139
src/main/java/ru/d3st0ny/adajency/AbstractAdajency.java
Normal file
|
|
@ -0,0 +1,139 @@
|
||||||
|
package ru.d3st0ny.adajency;
|
||||||
|
|
||||||
|
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||||
|
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||||
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import com.mojang.brigadier.tree.RootCommandNode;
|
||||||
|
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.command.PluginCommand;
|
||||||
|
|
||||||
|
import java.lang.reflect.Field;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
abstract class AbstractAdajency implements Adajency {
|
||||||
|
|
||||||
|
// ArgumentCommandNode#customSuggestions field
|
||||||
|
protected static final Field CUSTOM_SUGGESTIONS_FIELD;
|
||||||
|
|
||||||
|
// CommandNode#command
|
||||||
|
protected static final Field COMMAND_EXECUTE_FUNCTION_FIELD;
|
||||||
|
|
||||||
|
// CommandNode#children, CommandNode#literals, CommandNode#arguments fields
|
||||||
|
protected static final Field CHILDREN_FIELD;
|
||||||
|
protected static final Field LITERALS_FIELD;
|
||||||
|
protected static final Field ARGUMENTS_FIELD;
|
||||||
|
|
||||||
|
// An array of the CommandNode fields above: [#children, #literals, #arguments]
|
||||||
|
protected static final Field[] CHILDREN_FIELDS;
|
||||||
|
|
||||||
|
// Dummy instance of Command used to ensure the executable bit gets set on
|
||||||
|
// mock commands when they're encoded into data sent to the client
|
||||||
|
protected static final com.mojang.brigadier.Command<?> DUMMY_COMMAND;
|
||||||
|
protected static final SuggestionProvider<?> DUMMY_SUGGESTION_PROVIDER;
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
CUSTOM_SUGGESTIONS_FIELD = ArgumentCommandNode.class.getDeclaredField("customSuggestions");
|
||||||
|
CUSTOM_SUGGESTIONS_FIELD.setAccessible(true);
|
||||||
|
|
||||||
|
COMMAND_EXECUTE_FUNCTION_FIELD = CommandNode.class.getDeclaredField("command");
|
||||||
|
COMMAND_EXECUTE_FUNCTION_FIELD.setAccessible(true);
|
||||||
|
|
||||||
|
CHILDREN_FIELD = CommandNode.class.getDeclaredField("children");
|
||||||
|
LITERALS_FIELD = CommandNode.class.getDeclaredField("literals");
|
||||||
|
ARGUMENTS_FIELD = CommandNode.class.getDeclaredField("arguments");
|
||||||
|
CHILDREN_FIELDS = new Field[]{CHILDREN_FIELD, LITERALS_FIELD, ARGUMENTS_FIELD};
|
||||||
|
for (Field field : CHILDREN_FIELDS) {
|
||||||
|
field.setAccessible(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// should never be called
|
||||||
|
// if ReflectionCommodore: bukkit handling should override
|
||||||
|
// if PaperCommodore: this is only sent to the client, not used for actual command handling
|
||||||
|
DUMMY_COMMAND = (ctx) -> { throw new UnsupportedOperationException(); };
|
||||||
|
// should never be called - only used in clientbound root node, and the server impl will pass anything through
|
||||||
|
// SuggestionProviders#safelySwap (swap it for the ASK_SERVER provider) before sending
|
||||||
|
DUMMY_SUGGESTION_PROVIDER = (context, builder) -> { throw new UnsupportedOperationException(); };
|
||||||
|
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new ExceptionInInitializerError(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
protected static void removeChild(RootCommandNode root, String name) {
|
||||||
|
try {
|
||||||
|
for (Field field : CHILDREN_FIELDS) {
|
||||||
|
Map<String, ?> children = (Map<String, ?>) field.get(root);
|
||||||
|
children.remove(name);
|
||||||
|
}
|
||||||
|
} catch (ReflectiveOperationException e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static void setRequiredHackyFieldsRecursively(CommandNode<?> node, SuggestionProvider<?> suggestionProvider) {
|
||||||
|
// set command execution function so the server sets the executable flag on the command
|
||||||
|
try {
|
||||||
|
COMMAND_EXECUTE_FUNCTION_FIELD.set(node, DUMMY_COMMAND);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (suggestionProvider != null && node instanceof ArgumentCommandNode<?, ?> argumentNode) {
|
||||||
|
// set the custom suggestion provider field so tab completions work
|
||||||
|
try {
|
||||||
|
CUSTOM_SUGGESTIONS_FIELD.set(argumentNode, suggestionProvider);
|
||||||
|
} catch (IllegalAccessException e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CommandNode<?> child : node.getChildren()) {
|
||||||
|
setRequiredHackyFieldsRecursively(child, suggestionProvider);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected static <S> LiteralCommandNode<S> renameLiteralNode(LiteralCommandNode<S> node, String newLiteral) {
|
||||||
|
LiteralCommandNode<S> clone = new LiteralCommandNode<>(newLiteral, node.getCommand(), node.getRequirement(), node.getRedirect(), node.getRedirectModifier(), node.isFork());
|
||||||
|
for (CommandNode<S> child : node.getChildren()) {
|
||||||
|
clone.addChild(child);
|
||||||
|
}
|
||||||
|
return clone;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the aliases known for the given command.
|
||||||
|
*
|
||||||
|
* <p>This will include the main label, as well as defined aliases, and
|
||||||
|
* aliases including the fallback prefix added by Bukkit.</p>
|
||||||
|
*
|
||||||
|
* @param command the command
|
||||||
|
* @return the aliases
|
||||||
|
*/
|
||||||
|
protected static Collection<String> getAliases(Command command) {
|
||||||
|
Objects.requireNonNull(command, "command");
|
||||||
|
|
||||||
|
Stream<String> aliasesStream = Stream.concat(
|
||||||
|
Stream.of(command.getLabel()),
|
||||||
|
command.getAliases().stream()
|
||||||
|
);
|
||||||
|
|
||||||
|
if (command instanceof PluginCommand) {
|
||||||
|
String fallbackPrefix = ((PluginCommand) command).getPlugin().getName().toLowerCase().trim();
|
||||||
|
aliasesStream = aliasesStream.flatMap(alias -> Stream.of(
|
||||||
|
alias,
|
||||||
|
fallbackPrefix + ":" + alias
|
||||||
|
));
|
||||||
|
}
|
||||||
|
|
||||||
|
return aliasesStream.distinct().collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -66,7 +66,11 @@ public interface Adajency {
|
||||||
* @param command the command to read aliases from
|
* @param command the command to read aliases from
|
||||||
* @param node the argument data
|
* @param node the argument data
|
||||||
*/
|
*/
|
||||||
void register(Command command, LiteralCommandNode<?> node);
|
default void register(Command command, LiteralCommandNode<?> node) {
|
||||||
|
Objects.requireNonNull(command, "command");
|
||||||
|
Objects.requireNonNull(node, "node");
|
||||||
|
register(command, node, command::testPermissionSilent);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers the provided argument data to the dispatcher, against all
|
* Registers the provided argument data to the dispatcher, against all
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ package ru.d3st0ny.adajency;
|
||||||
import org.bukkit.plugin.Plugin;
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Factory for obtaining instances of {@link Adajency}.
|
* Factory for obtaining instances of {@link Adajency}.
|
||||||
|
|
@ -12,21 +13,40 @@ public final class AdajencyProvider {
|
||||||
throw new AssertionError();
|
throw new AssertionError();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final Throwable SETUP_EXCEPTION = checkSupported();
|
private static final Function<Plugin, Adajency> PROVIDER = checkSupported();
|
||||||
|
|
||||||
private static Throwable checkSupported() {
|
private static Function<Plugin, Adajency> checkSupported() {
|
||||||
try {
|
try {
|
||||||
Class.forName("com.mojang.brigadier.CommandDispatcher");
|
Class.forName("com.mojang.brigadier.CommandDispatcher");
|
||||||
AdajencyImpl.ensureSetup();
|
|
||||||
MinecraftArgumentType.ensureSetup();
|
|
||||||
return null;
|
|
||||||
} catch (Throwable e) {
|
} catch (Throwable e) {
|
||||||
|
printDebugInfo(e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// try the paper impl
|
||||||
|
try {
|
||||||
|
PaperAdajency.ensureSetup();
|
||||||
|
return PaperAdajency::new;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
printDebugInfo(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
// try reflection impl
|
||||||
|
try {
|
||||||
|
ReflectionAdajency.ensureSetup();
|
||||||
|
return ReflectionAdajency::new;
|
||||||
|
} catch (Throwable e) {
|
||||||
|
printDebugInfo(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void printDebugInfo(Throwable e) {
|
||||||
if (System.getProperty("adajency.debug") != null) {
|
if (System.getProperty("adajency.debug") != null) {
|
||||||
System.err.println("Exception while initialising adajency:");
|
System.err.println("Exception while initialising adajency:");
|
||||||
e.printStackTrace(System.err);
|
e.printStackTrace(System.err);
|
||||||
}
|
}
|
||||||
return e;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -35,7 +55,7 @@ public final class AdajencyProvider {
|
||||||
* @return true if adajency is supported.
|
* @return true if adajency is supported.
|
||||||
*/
|
*/
|
||||||
public static boolean isSupported() {
|
public static boolean isSupported() {
|
||||||
return SETUP_EXCEPTION == null;
|
return PROVIDER != null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,9 +68,12 @@ public final class AdajencyProvider {
|
||||||
*/
|
*/
|
||||||
public static Adajency getAdajency(Plugin plugin) throws BrigadierUnsupportedException {
|
public static Adajency getAdajency(Plugin plugin) throws BrigadierUnsupportedException {
|
||||||
Objects.requireNonNull(plugin, "plugin");
|
Objects.requireNonNull(plugin, "plugin");
|
||||||
if (SETUP_EXCEPTION != null) {
|
if (PROVIDER == null) {
|
||||||
throw new BrigadierUnsupportedException("Brigadier is not supported by the server.", SETUP_EXCEPTION);
|
throw new BrigadierUnsupportedException(
|
||||||
|
"Brigadier is not supported by the server. " +
|
||||||
|
"Set -Dadajency.debug=true for debug info."
|
||||||
|
);
|
||||||
}
|
}
|
||||||
return new AdajencyImpl(plugin);
|
return PROVIDER.apply(plugin);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ package ru.d3st0ny.adajency;
|
||||||
*/
|
*/
|
||||||
public final class BrigadierUnsupportedException extends UnsupportedOperationException {
|
public final class BrigadierUnsupportedException extends UnsupportedOperationException {
|
||||||
|
|
||||||
public BrigadierUnsupportedException(String message, Throwable cause) {
|
BrigadierUnsupportedException(String message) {
|
||||||
super(message, cause);
|
super(message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
96
src/main/java/ru/d3st0ny/adajency/PaperAdajency.java
Normal file
96
src/main/java/ru/d3st0ny/adajency/PaperAdajency.java
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
package ru.d3st0ny.adajency;
|
||||||
|
|
||||||
|
import com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent;
|
||||||
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
|
import com.mojang.brigadier.tree.CommandNode;
|
||||||
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
|
import com.mojang.brigadier.tree.RootCommandNode;
|
||||||
|
|
||||||
|
import org.bukkit.command.Command;
|
||||||
|
import org.bukkit.entity.Player;
|
||||||
|
import org.bukkit.event.EventHandler;
|
||||||
|
import org.bukkit.event.Listener;
|
||||||
|
import org.bukkit.plugin.Plugin;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Predicate;
|
||||||
|
|
||||||
|
final class PaperAdajency extends AbstractAdajency implements Adajency, Listener {
|
||||||
|
|
||||||
|
static {
|
||||||
|
try {
|
||||||
|
Class.forName("com.destroystokyo.paper.event.brigadier.AsyncPlayerSendCommandsEvent");
|
||||||
|
} catch (ClassNotFoundException e) {
|
||||||
|
throw new UnsupportedOperationException("Not running on modern Paper!", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private final List<AdajencyCommand> commands = new ArrayList<>();
|
||||||
|
|
||||||
|
PaperAdajency(Plugin plugin) {
|
||||||
|
plugin.getServer().getPluginManager().registerEvents(this, plugin);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(LiteralCommandNode<?> node) {
|
||||||
|
Objects.requireNonNull(node, "node");
|
||||||
|
this.commands.add(new AdajencyCommand(node, null));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void register(Command command, LiteralCommandNode<?> node, Predicate<? super Player> permissionTest) {
|
||||||
|
Objects.requireNonNull(command, "command");
|
||||||
|
Objects.requireNonNull(node, "node");
|
||||||
|
Objects.requireNonNull(permissionTest, "permissionTest");
|
||||||
|
|
||||||
|
try {
|
||||||
|
setRequiredHackyFieldsRecursively(node, DUMMY_SUGGESTION_PROVIDER);
|
||||||
|
} catch (Throwable e) {
|
||||||
|
e.printStackTrace();
|
||||||
|
}
|
||||||
|
|
||||||
|
Collection<String> aliases = getAliases(command);
|
||||||
|
if (!aliases.contains(node.getLiteral())) {
|
||||||
|
node = renameLiteralNode(node, command.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (String alias : aliases) {
|
||||||
|
if (node.getLiteral().equals(alias)) {
|
||||||
|
this.commands.add(new AdajencyCommand(node, permissionTest));
|
||||||
|
} else {
|
||||||
|
LiteralCommandNode<Object> redirectNode = LiteralArgumentBuilder.literal(alias)
|
||||||
|
.redirect((LiteralCommandNode<Object>) node)
|
||||||
|
.build();
|
||||||
|
this.commands.add(new AdajencyCommand(redirectNode, permissionTest));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
@SuppressWarnings("deprecation") // draft API, ok...
|
||||||
|
public void onPlayerSendCommandsEvent(AsyncPlayerSendCommandsEvent<?> event) {
|
||||||
|
if (event.isAsynchronous() || !event.hasFiredAsync()) {
|
||||||
|
for (AdajencyCommand command : this.commands) {
|
||||||
|
command.apply(event.getPlayer(), event.getCommandNode());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private record AdajencyCommand(LiteralCommandNode<?> node, Predicate<? super Player> permissionTest) {
|
||||||
|
@SuppressWarnings({"unchecked", "rawtypes"})
|
||||||
|
public void apply(Player player, RootCommandNode<?> root) {
|
||||||
|
if (this.permissionTest != null && !this.permissionTest.test(player)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
removeChild(root, this.node.getName());
|
||||||
|
root.addChild((CommandNode) this.node);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ensureSetup() {
|
||||||
|
// do nothing - this is only called to trigger the static initializer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -3,14 +3,11 @@ package ru.d3st0ny.adajency;
|
||||||
import com.mojang.brigadier.CommandDispatcher;
|
import com.mojang.brigadier.CommandDispatcher;
|
||||||
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
|
||||||
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
import com.mojang.brigadier.suggestion.SuggestionProvider;
|
||||||
import com.mojang.brigadier.tree.ArgumentCommandNode;
|
|
||||||
import com.mojang.brigadier.tree.CommandNode;
|
|
||||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||||
import com.mojang.brigadier.tree.RootCommandNode;
|
import com.mojang.brigadier.tree.RootCommandNode;
|
||||||
|
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
import org.bukkit.command.Command;
|
import org.bukkit.command.Command;
|
||||||
import org.bukkit.command.PluginCommand;
|
|
||||||
import org.bukkit.entity.Player;
|
import org.bukkit.entity.Player;
|
||||||
import org.bukkit.event.EventHandler;
|
import org.bukkit.event.EventHandler;
|
||||||
import org.bukkit.event.Listener;
|
import org.bukkit.event.Listener;
|
||||||
|
|
@ -26,14 +23,12 @@ import java.util.Arrays;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
|
||||||
|
|
||||||
final class AdajencyImpl implements Adajency {
|
final class ReflectionAdajency extends AbstractAdajency implements Adajency {
|
||||||
|
|
||||||
// obc.CraftServer#console field
|
// obc.CraftServer#console field
|
||||||
private static final Field CONSOLE_FIELD;
|
private static final Field CONSOLE_FIELD;
|
||||||
|
|
@ -47,26 +42,12 @@ final class AdajencyImpl implements Adajency {
|
||||||
// obc.command.BukkitCommandWrapper constructor
|
// obc.command.BukkitCommandWrapper constructor
|
||||||
private static final Constructor<?> COMMAND_WRAPPER_CONSTRUCTOR;
|
private static final Constructor<?> COMMAND_WRAPPER_CONSTRUCTOR;
|
||||||
|
|
||||||
// ArgumentCommandNode#customSuggestions field
|
|
||||||
private static final Field CUSTOM_SUGGESTIONS_FIELD;
|
|
||||||
|
|
||||||
// CommandNode#command
|
|
||||||
private static final Field COMMAND_EXECUTE_FUNCTION_FIELD;
|
|
||||||
|
|
||||||
// CommandNode#children, CommandNode#literals, CommandNode#arguments fields
|
|
||||||
private static final Field CHILDREN_FIELD;
|
|
||||||
private static final Field LITERALS_FIELD;
|
|
||||||
private static final Field ARGUMENTS_FIELD;
|
|
||||||
|
|
||||||
// An array of the CommandNode fields above: [#children, #literals, #arguments]
|
|
||||||
private static final Field[] CHILDREN_FIELDS;
|
|
||||||
|
|
||||||
// Dummy instance of Command used to ensure the executable bit gets set on
|
|
||||||
// mock commands when they're encoded into data sent to the client
|
|
||||||
private static final com.mojang.brigadier.Command<?> DUMMY_COMMAND;
|
|
||||||
|
|
||||||
static {
|
static {
|
||||||
try {
|
try {
|
||||||
|
if (ReflectionUtil.minecraftVersion() >= 19) {
|
||||||
|
throw new UnsupportedOperationException("ReflectionAdajency is not supported on MC 1.19 or above. Switch to Paper :)");
|
||||||
|
}
|
||||||
|
|
||||||
final Class<?> minecraftServer;
|
final Class<?> minecraftServer;
|
||||||
final Class<?> commandDispatcher;
|
final Class<?> commandDispatcher;
|
||||||
|
|
||||||
|
|
@ -97,22 +78,6 @@ final class AdajencyImpl implements Adajency {
|
||||||
Class<?> commandWrapperClass = ReflectionUtil.obcClass("command.BukkitCommandWrapper");
|
Class<?> commandWrapperClass = ReflectionUtil.obcClass("command.BukkitCommandWrapper");
|
||||||
COMMAND_WRAPPER_CONSTRUCTOR = commandWrapperClass.getConstructor(craftServer, Command.class);
|
COMMAND_WRAPPER_CONSTRUCTOR = commandWrapperClass.getConstructor(craftServer, Command.class);
|
||||||
|
|
||||||
CUSTOM_SUGGESTIONS_FIELD = ArgumentCommandNode.class.getDeclaredField("customSuggestions");
|
|
||||||
CUSTOM_SUGGESTIONS_FIELD.setAccessible(true);
|
|
||||||
|
|
||||||
COMMAND_EXECUTE_FUNCTION_FIELD = CommandNode.class.getDeclaredField("command");
|
|
||||||
COMMAND_EXECUTE_FUNCTION_FIELD.setAccessible(true);
|
|
||||||
|
|
||||||
CHILDREN_FIELD = CommandNode.class.getDeclaredField("children");
|
|
||||||
LITERALS_FIELD = CommandNode.class.getDeclaredField("literals");
|
|
||||||
ARGUMENTS_FIELD = CommandNode.class.getDeclaredField("arguments");
|
|
||||||
CHILDREN_FIELDS = new Field[]{CHILDREN_FIELD, LITERALS_FIELD, ARGUMENTS_FIELD};
|
|
||||||
for (Field field : CHILDREN_FIELDS) {
|
|
||||||
field.setAccessible(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
DUMMY_COMMAND = (ctx) -> { throw new UnsupportedOperationException(); };
|
|
||||||
|
|
||||||
} catch (ReflectiveOperationException e) {
|
} catch (ReflectiveOperationException e) {
|
||||||
throw new ExceptionInInitializerError(e);
|
throw new ExceptionInInitializerError(e);
|
||||||
}
|
}
|
||||||
|
|
@ -121,7 +86,7 @@ final class AdajencyImpl implements Adajency {
|
||||||
private final Plugin plugin;
|
private final Plugin plugin;
|
||||||
private final List<LiteralCommandNode<?>> registeredNodes = new ArrayList<>();
|
private final List<LiteralCommandNode<?>> registeredNodes = new ArrayList<>();
|
||||||
|
|
||||||
AdajencyImpl(Plugin plugin) {
|
ReflectionAdajency(Plugin plugin) {
|
||||||
this.plugin = plugin;
|
this.plugin = plugin;
|
||||||
this.plugin.getServer().getPluginManager().registerEvents(new ServerReloadListener(this), this.plugin);
|
this.plugin.getServer().getPluginManager().registerEvents(new ServerReloadListener(this), this.plugin);
|
||||||
}
|
}
|
||||||
|
|
@ -172,68 +137,17 @@ final class AdajencyImpl implements Adajency {
|
||||||
if (node.getLiteral().equals(alias)) {
|
if (node.getLiteral().equals(alias)) {
|
||||||
register(node);
|
register(node);
|
||||||
} else {
|
} else {
|
||||||
register(LiteralArgumentBuilder.literal(alias).redirect((LiteralCommandNode<Object>)node).build());
|
register(LiteralArgumentBuilder.literal(alias).redirect((LiteralCommandNode<Object>) node).build());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.plugin.getServer().getPluginManager().registerEvents(new CommandDataSendListener(command, permissionTest), this.plugin);
|
this.plugin.getServer().getPluginManager().registerEvents(new CommandDataSendListener(command, permissionTest), this.plugin);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void register(Command command, LiteralCommandNode<?> node) {
|
|
||||||
Objects.requireNonNull(command, "command");
|
|
||||||
Objects.requireNonNull(node, "node");
|
|
||||||
|
|
||||||
register(command, node, command::testPermissionSilent);
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings({"unchecked", "rawtypes"})
|
|
||||||
private static void removeChild(RootCommandNode root, String name) {
|
|
||||||
try {
|
|
||||||
for (Field field : CHILDREN_FIELDS) {
|
|
||||||
Map<String, ?> children = (Map<String, ?>) field.get(root);
|
|
||||||
children.remove(name);
|
|
||||||
}
|
|
||||||
} catch (ReflectiveOperationException e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void setRequiredHackyFieldsRecursively(CommandNode<?> node, SuggestionProvider<?> suggestionProvider) {
|
|
||||||
// set command execution function so the server sets the executable flag on the command
|
|
||||||
try {
|
|
||||||
COMMAND_EXECUTE_FUNCTION_FIELD.set(node, DUMMY_COMMAND);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (node instanceof ArgumentCommandNode<?, ?> argumentNode) {
|
|
||||||
|
|
||||||
// set the custom suggestion provider field so tab completions work
|
|
||||||
try {
|
|
||||||
CUSTOM_SUGGESTIONS_FIELD.set(argumentNode, suggestionProvider);
|
|
||||||
} catch (IllegalAccessException e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for (CommandNode<?> child : node.getChildren()) {
|
|
||||||
setRequiredHackyFieldsRecursively(child, suggestionProvider);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static <S> LiteralCommandNode<S> renameLiteralNode(LiteralCommandNode<S> node, String newLiteral) {
|
|
||||||
LiteralCommandNode<S> clone = new LiteralCommandNode<>(newLiteral, node.getCommand(), node.getRequirement(), node.getRedirect(), node.getRedirectModifier(), node.isFork());
|
|
||||||
for (CommandNode<S> child : node.getChildren()) {
|
|
||||||
clone.addChild(child);
|
|
||||||
}
|
|
||||||
return clone;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listens for server (re)loads, and re-adds all registered nodes to the dispatcher.
|
* Listens for server (re)loads, and re-adds all registered nodes to the dispatcher.
|
||||||
*/
|
*/
|
||||||
private record ServerReloadListener(AdajencyImpl adajency) implements Listener {
|
private record ServerReloadListener(ReflectionAdajency adajency) implements Listener {
|
||||||
|
|
||||||
@SuppressWarnings({"rawtypes", "unchecked"})
|
@SuppressWarnings({"rawtypes", "unchecked"})
|
||||||
@EventHandler
|
@EventHandler
|
||||||
|
|
@ -276,36 +190,7 @@ final class AdajencyImpl implements Adajency {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the aliases known for the given command.
|
|
||||||
*
|
|
||||||
* <p>This will include the main label, as well as defined aliases, and
|
|
||||||
* aliases including the fallback prefix added by Bukkit.</p>
|
|
||||||
*
|
|
||||||
* @param command the command
|
|
||||||
* @return the aliases
|
|
||||||
*/
|
|
||||||
private static Collection<String> getAliases(Command command) {
|
|
||||||
Objects.requireNonNull(command, "command");
|
|
||||||
|
|
||||||
Stream<String> aliasesStream = Stream.concat(
|
|
||||||
Stream.of(command.getLabel()),
|
|
||||||
command.getAliases().stream()
|
|
||||||
);
|
|
||||||
|
|
||||||
if (command instanceof PluginCommand) {
|
|
||||||
String fallbackPrefix = ((PluginCommand) command).getPlugin().getName().toLowerCase().trim();
|
|
||||||
aliasesStream = aliasesStream.flatMap(alias -> Stream.of(
|
|
||||||
alias,
|
|
||||||
fallbackPrefix + ":" + alias
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
return aliasesStream.distinct().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
static void ensureSetup() {
|
static void ensureSetup() {
|
||||||
// do nothing - this is only called to trigger the static initializer
|
// do nothing - this is only called to trigger the static initializer
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -4,7 +4,7 @@ import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import org.bukkit.Bukkit;
|
import org.bukkit.Bukkit;
|
||||||
|
|
||||||
final class ReflectionUtil {
|
public final class ReflectionUtil {
|
||||||
private static final String SERVER_VERSION = getServerVersion();
|
private static final String SERVER_VERSION = getServerVersion();
|
||||||
|
|
||||||
private static String getServerVersion() {
|
private static String getServerVersion() {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue