13
0
geforkt von Mirrors/Velocity

New command API (#330)

Dieser Commit ist enthalten in:
Hugo Manrique 2020-07-29 09:43:16 +02:00 committet von GitHub
Ursprung 5a515f37a3
Commit 6cc6e0f641
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: 4AEE18F83AFDEB23
36 geänderte Dateien mit 1836 neuen und 524 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,48 @@
package com.velocitypowered.api.command;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.tree.LiteralCommandNode;
/**
* A command that uses Brigadier for parsing the command and
* providing suggestions to the client.
*/
public final class BrigadierCommand implements Command {
/**
* Return code used by a {@link com.mojang.brigadier.Command} to indicate
* the command execution should be forwarded to the backend server.
*/
public static final int FORWARD = 0xF6287429;
private final LiteralCommandNode<CommandSource> node;
/**
* Constructs a {@link BrigadierCommand} from the node returned by
* the given builder.
*
* @param builder the {@link LiteralCommandNode} builder
*/
public BrigadierCommand(final LiteralArgumentBuilder<CommandSource> builder) {
this(Preconditions.checkNotNull(builder, "builder").build());
}
/**
* Constructs a {@link BrigadierCommand} from the given command node.
*
* @param node the command node
*/
public BrigadierCommand(final LiteralCommandNode<CommandSource> node) {
this.node = Preconditions.checkNotNull(node, "node");
}
/**
* Returns the literal node for this command.
*
* @return the command node
*/
public LiteralCommandNode<CommandSource> getNode() {
return node;
}
}

Datei anzeigen

@ -1,59 +1,90 @@
package com.velocitypowered.api.command; package com.velocitypowered.api.command;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.proxy.Player;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
* Represents a command that can be executed by a {@link CommandSource}, such as a {@link * Represents a command that can be executed by a {@link CommandSource}
* com.velocitypowered.api.proxy.Player} or the console. * such as a {@link Player} or the console.
*
* <p>Velocity 1.1.0 introduces specialized command subinterfaces to separate
* command parsing concerns. These include, in order of preference:
*
* <ul>
* <li>{@link BrigadierCommand}, which supports parameterized arguments and
* specialized execution, tab complete suggestions and permission-checking logic.
*
* <li>{@link SimpleCommand}, modelled after the convention popularized by
* Bukkit and BungeeCord. Older classes directly implementing {@link Command}
* are suggested to migrate to this interface.
*
* <li>{@link RawCommand}, useful for bolting on external command frameworks
* to Velocity.
*
* </ul>
*
* <p>For this reason, the legacy {@code execute}, {@code suggest} and
* {@code hasPermission} methods are deprecated and will be removed
* in Velocity 2.0.0. We suggest implementing one of the more specific
* subinterfaces instead.
* The legacy methods are executed by a {@link CommandManager} if and only if
* the given command <b>directly</b> implements this interface.
*/ */
public interface Command { public interface Command {
/** /**
* Executes the command for the specified {@link CommandSource}. * Executes the command for the specified source.
* *
* @param source the source of this command * @param source the source to execute the command for
* @param args the arguments for this command * @param args the arguments for the command
* @deprecated see {@link Command}
*/ */
void execute(CommandSource source, String @NonNull [] args); @Deprecated
default void execute(final CommandSource source, final String @NonNull [] args) {
throw new UnsupportedOperationException();
}
/** /**
* Provides tab complete suggestions for a command for a specified {@link CommandSource}. * Provides tab complete suggestions for the specified source.
* *
* @param source the source to run the command for * @param source the source to execute the command for
* @param currentArgs the current, partial arguments for this command * @param currentArgs the partial arguments for the command
* @return tab complete suggestions * @return the tab complete suggestions
* @deprecated see {@link Command}
*/ */
default List<String> suggest(CommandSource source, String @NonNull [] currentArgs) { @Deprecated
default List<String> suggest(final CommandSource source, final String @NonNull [] currentArgs) {
return ImmutableList.of(); return ImmutableList.of();
} }
/** /**
* Provides tab complete suggestions for a command for a specified {@link CommandSource}. * Provides tab complete suggestions for the specified source.
* *
* @param source the source to run the command for * @param source the source to execute the command for
* @param currentArgs the current, partial arguments for this command * @param currentArgs the partial arguments for the command
* @return tab complete suggestions * @return the tab complete suggestions
* @deprecated see {@link Command}
*/ */
default CompletableFuture<List<String>> suggestAsync(CommandSource source, @Deprecated
default CompletableFuture<List<String>> suggestAsync(final CommandSource source,
String @NonNull [] currentArgs) { String @NonNull [] currentArgs) {
return CompletableFuture.completedFuture(suggest(source, currentArgs)); return CompletableFuture.completedFuture(suggest(source, currentArgs));
} }
/** /**
* Tests to check if the {@code source} has permission to use this command with the provided * Tests to check if the source has permission to perform the command with
* {@code args}. * the provided arguments.
* *
* <p>If this method returns false, the handling will be forwarded onto * @param source the source to execute the command for
* the players current server.</p> * @param args the arguments for the command
* * @return {@code true} if the source has permission
* @param source the source of the command * @deprecated see {@link Command}
* @param args the arguments for this command
* @return whether the source has permission
*/ */
default boolean hasPermission(CommandSource source, String @NonNull [] args) { @Deprecated
default boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return true; return true;
} }
} }

Datei anzeigen

@ -0,0 +1,23 @@
package com.velocitypowered.api.command;
/**
* Provides information related to the possible execution of a {@link Command}.
*
* @param <T> the type of the arguments
*/
public interface CommandInvocation<T> {
/**
* Returns the source to execute the command for.
*
* @return the command source
*/
CommandSource source();
/**
* Returns the arguments after the command alias.
*
* @return the command arguments
*/
T arguments();
}

Datei anzeigen

@ -1,87 +1,125 @@
package com.velocitypowered.api.command; package com.velocitypowered.api.command;
import com.velocitypowered.api.event.command.CommandExecuteEvent;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
/** /**
* Represents an interface to register a command executor with the proxy. * Handles the registration and execution of commands.
*/ */
public interface CommandManager { public interface CommandManager {
/** /**
* Registers the specified command with the manager with the specified aliases. * Returns a builder to create a {@link CommandMeta} with
* the given alias.
*
* @param alias the first command alias
* @return a {@link CommandMeta} builder
*/
CommandMeta.Builder metaBuilder(String alias);
/**
* Returns a builder to create a {@link CommandMeta} for
* the given Brigadier command.
*
* @param command the command
* @return a {@link CommandMeta} builder
*/
CommandMeta.Builder metaBuilder(BrigadierCommand command);
/**
* Registers the specified command with the specified aliases.
* *
* @param command the command to register * @param command the command to register
* @param aliases the alias to use * @param aliases the command aliases
* *
* @throws IllegalArgumentException if one of the given aliases is already registered
* @deprecated This method requires at least one alias, but this is only enforced at runtime. * @deprecated This method requires at least one alias, but this is only enforced at runtime.
* Prefer {@link #register(String, Command, String...)} instead. * Prefer {@link #register(String, Command, String...)}
*/ */
@Deprecated @Deprecated
void register(Command command, String... aliases); void register(Command command, String... aliases);
/** /**
* Registers the specified command with the manager with the specified aliases. * Registers the specified command with the specified aliases.
* *
* @param alias the first alias to register * @param alias the first command alias
* @param command the command to register * @param command the command to register
* @param otherAliases the other aliases to use * @param otherAliases additional aliases
* @throws IllegalArgumentException if one of the given aliases is already registered
* @deprecated Prefer {@link #register(CommandMeta, Command)} instead.
*/ */
@Deprecated
void register(String alias, Command command, String... otherAliases); void register(String alias, Command command, String... otherAliases);
/** /**
* Unregisters a command. * Registers the specified Brigadier command.
*
* @param command the command to register
* @throws IllegalArgumentException if the node alias is already registered
*/
void register(BrigadierCommand command);
/**
* Registers the specified command with the given metadata.
*
* @param meta the command metadata
* @param command the command to register
* @throws IllegalArgumentException if one of the given aliases is already registered
*/
void register(CommandMeta meta, Command command);
/**
* Unregisters the specified command alias from the manager, if registered.
* *
* @param alias the command alias to unregister * @param alias the command alias to unregister
*/ */
void unregister(String alias); void unregister(String alias);
/** /**
* Calls CommandExecuteEvent and attempts to execute a command using the specified {@code cmdLine} * Attempts to execute a command from the given {@code cmdLine} in
* in a blocking fashion. * a blocking fashion.
* *
* @param source the command's source * @param source the source to execute the command for
* @param cmdLine the command to run * @param cmdLine the command to run
* @return true if the command was found and executed, false if it was not * @return {@code true} if the command was found and executed
* * @deprecated this method blocks the current thread during the event call and
* @deprecated This method will block current thread during event call and command execution. * the command execution. Prefer {@link #executeAsync(CommandSource, String)}
* Prefer {@link #executeAsync(CommandSource, String)} instead. * instead.
*/ */
@Deprecated @Deprecated
boolean execute(CommandSource source, String cmdLine); boolean execute(CommandSource source, String cmdLine);
/** /**
* Attempts to execute a command using the specified {@code cmdLine} in a blocking fashion without * Attempts to execute a command from the given {@code cmdLine} without
* calling CommandExecuteEvent. * firing a {@link CommandExecuteEvent} in a blocking fashion.
* *
* @param source the command's source * @param source the source to execute the command for
* @param cmdLine the command to run * @param cmdLine the command to run
* @return true if the command was found and executed, false if it was not * @return {@code true} if the command was found and executed
* * @deprecated this methods blocks the current thread during the command execution.
* @deprecated This method will block current thread during event and command execution.
* Prefer {@link #executeImmediatelyAsync(CommandSource, String)} instead. * Prefer {@link #executeImmediatelyAsync(CommandSource, String)} instead.
*/ */
@Deprecated @Deprecated
boolean executeImmediately(CommandSource source, String cmdLine); boolean executeImmediately(CommandSource source, String cmdLine);
/** /**
* Calls CommandExecuteEvent and attempts to execute a command from the specified {@code cmdLine} * Attempts to asynchronously execute a command from the given {@code cmdLine}.
* async.
* *
* @param source the command's source * @param source the source to execute the command for
* @param cmdLine the command to run * @param cmdLine the command to run
* @return A future that will be completed with the result of the command execution. * @return a future that may be completed with the result of the command execution.
* Can be completed exceptionally if exception was thrown during execution. * Can be completed exceptionally if an exception is thrown during execution.
*/ */
CompletableFuture<Boolean> executeAsync(CommandSource source, String cmdLine); CompletableFuture<Boolean> executeAsync(CommandSource source, String cmdLine);
/** /**
* Attempts to execute a command from the specified {@code cmdLine} async * Attempts to asynchronously execute a command from the given {@code cmdLine}
* without calling CommandExecuteEvent. * without firing a {@link CommandExecuteEvent}.
* *
* @param source the command's source * @param source the source to execute the command for
* @param cmdLine the command to run * @param cmdLine the command to run
* @return A future that will be completed with the result of the command execution. * @return a future that may be completed with the result of the command execution.
* Can be completed exceptionally if exception was thrown during execution. * Can be completed exceptionally if an exception is thrown during execution.
*/ */
CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine); CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine);
} }

Datei anzeigen

@ -0,0 +1,57 @@
package com.velocitypowered.api.command;
import com.mojang.brigadier.tree.CommandNode;
import java.util.Collection;
/**
* Contains metadata for a {@link Command}.
*/
public interface CommandMeta {
/**
* Returns a non-empty collection containing the case-insensitive aliases
* used to execute the command.
*
* @return the command aliases
*/
Collection<String> getAliases();
/**
* Returns a collection containing command nodes that provide additional
* argument metadata and tab-complete suggestions.
* Note some {@link Command} implementations may not support hinting.
*
* @return the hinting command nodes
*/
Collection<CommandNode<CommandSource>> getHints();
/**
* Provides a fluent interface to create {@link CommandMeta}s.
*/
interface Builder {
/**
* Specifies additional aliases that can be used to execute the command.
*
* @param aliases the command aliases
* @return this builder, for chaining
*/
Builder aliases(String... aliases);
/**
* Specifies a command node providing additional argument metadata and
* tab-complete suggestions.
*
* @param node the command node
* @return this builder, for chaining
*/
Builder hint(CommandNode<CommandSource> node);
/**
* Returns a newly-created {@link CommandMeta} based on the specified parameters.
*
* @return the built {@link CommandMeta}
*/
CommandMeta build();
}
}

Datei anzeigen

@ -0,0 +1,54 @@
package com.velocitypowered.api.command;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.concurrent.CompletableFuture;
/**
* A command that can be executed with arbitrary arguments.
*
* @param <I> the type of the command invocation object
*/
public interface InvocableCommand<I extends CommandInvocation<?>> extends Command {
/**
* Executes the command for the specified invocation.
*
* @param invocation the invocation context
*/
void execute(I invocation);
/**
* Provides tab complete suggestions for the specified invocation.
*
* @param invocation the invocation context
* @return the tab complete suggestions
*/
default List<String> suggest(final I invocation) {
return ImmutableList.of();
}
/**
* Provides tab complete suggestions for the specified invocation.
*
* @param invocation the invocation context
* @return the tab complete suggestions
* @implSpec defaults to wrapping the value returned by {@link #suggest(CommandInvocation)}
*/
default CompletableFuture<List<String>> suggestAsync(final I invocation) {
return CompletableFuture.completedFuture(suggest(invocation));
}
/**
* Tests to check if the source has permission to perform the specified invocation.
*
* <p>If the method returns {@code false}, the handling is forwarded onto
* the players current server.
*
* @param invocation the invocation context
* @return {@code true} if the source has permission
*/
default boolean hasPermission(final I invocation) {
return true;
}
}

Datei anzeigen

@ -1,67 +1,97 @@
package com.velocitypowered.api.command; package com.velocitypowered.api.command;
import com.google.common.collect.ImmutableList;
import java.util.List; import java.util.List;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
/** /**
* A specialized sub-interface of {@code Command} which indicates that the proxy should pass a * A specialized sub-interface of {@code Command} which indicates the proxy should pass
* raw command to the command. This is useful for bolting on external command frameworks to * the command and its arguments directly without further processing.
* Velocity. * This is useful for bolting on external command frameworks to Velocity.
*/ */
public interface RawCommand extends Command { public interface RawCommand extends InvocableCommand<RawCommand.Invocation> {
/**
* Executes the command for the specified {@link CommandSource}.
*
* @param source the source of this command
* @param commandLine the full command line after the command name
*/
void execute(CommandSource source, String commandLine);
default void execute(CommandSource source, String @NonNull [] args) { /**
* Executes the command for the specified source.
*
* @param source the source to execute the command for
* @param cmdLine the arguments for the command
* @deprecated see {@link Command}
*/
@Deprecated
default void execute(final CommandSource source, final String cmdLine) {
throw new UnsupportedOperationException();
}
@Deprecated
@Override
default void execute(final CommandSource source, final String @NonNull [] args) {
execute(source, String.join(" ", args)); execute(source, String.join(" ", args));
} }
/** @Override
* Provides tab complete suggestions for a command for a specified {@link CommandSource}. default void execute(Invocation invocation) {
* // Guarantees ABI compatibility
* @param source the source to run the command for
* @param currentLine the current, partial command line for this command
* @return tab complete suggestions
*/
default CompletableFuture<List<String>> suggest(CommandSource source, String currentLine) {
return CompletableFuture.completedFuture(ImmutableList.of());
} }
/**
* Provides tab complete suggestions for the specified source.
*
* @param source the source to execute the command for
* @param currentArgs the partial arguments for the command
* @return the tab complete suggestions
* @deprecated see {@link Command}
*/
@Deprecated
default CompletableFuture<List<String>> suggest(final CommandSource source,
final String currentArgs) {
// This method even has an inconsistent return type
throw new UnsupportedOperationException();
}
@Deprecated
@Override @Override
default List<String> suggest(CommandSource source, String @NonNull [] currentArgs) { default List<String> suggest(final CommandSource source, final String @NonNull [] currentArgs) {
return suggestAsync(source, currentArgs).join(); return suggestAsync(source, currentArgs).join();
} }
@Deprecated
@Override @Override
default CompletableFuture<List<String>> suggestAsync(CommandSource source, default CompletableFuture<List<String>> suggestAsync(final CommandSource source,
String @NonNull [] currentArgs) { final String @NonNull [] currentArgs) {
return suggest(source, String.join(" ", currentArgs)); return suggest(source, String.join(" ", currentArgs));
} }
/**
* Tests to check if the source has permission to perform the command with
* the provided arguments.
*
* @param source the source to execute the command for
* @param cmdLine the arguments for the command
* @return {@code true} if the source has permission
* @deprecated see {@link Command}
*/
@Deprecated
default boolean hasPermission(final CommandSource source, final String cmdLine) {
throw new UnsupportedOperationException();
}
@Deprecated
@Override @Override
default boolean hasPermission(CommandSource source, String @NonNull [] args) { default boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return hasPermission(source, String.join(" ", args)); return hasPermission(source, String.join(" ", args));
} }
/** /**
* Tests to check if the {@code source} has permission to use this command with the provided * Contains the invocation data for a raw command.
* {@code args}.
*
* <p>If this method returns false, the handling will be forwarded onto
* the players current server.</p>
*
* @param source the source of the command
* @param commandLine the arguments for this command
* @return whether the source has permission
*/ */
default boolean hasPermission(CommandSource source, String commandLine) { interface Invocation extends CommandInvocation<String> {
return true;
/**
* Returns the used alias to execute the command.
*
* @return the used command alias
*/
String alias();
} }
} }

Datei anzeigen

@ -0,0 +1,20 @@
package com.velocitypowered.api.command;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
* A simple command, modelled after the convention popularized by
* Bukkit and BungeeCord.
*
* <p>Prefer using {@link BrigadierCommand}, which is also
* backwards-compatible with older clients.
*/
public interface SimpleCommand extends InvocableCommand<SimpleCommand.Invocation> {
/**
* Contains the invocation data for a simple command.
*/
interface Invocation extends CommandInvocation<String @NonNull []> {
}
}

Datei anzeigen

@ -1,4 +1,4 @@
/** /**
* Provides a simple command framework. * Provides a command framework.
*/ */
package com.velocitypowered.api.command; package com.velocitypowered.api.command;

Datei anzeigen

@ -21,11 +21,11 @@ import com.velocitypowered.api.util.ProxyVersion;
import com.velocitypowered.api.util.bossbar.BossBar; import com.velocitypowered.api.util.bossbar.BossBar;
import com.velocitypowered.api.util.bossbar.BossBarColor; import com.velocitypowered.api.util.bossbar.BossBarColor;
import com.velocitypowered.api.util.bossbar.BossBarOverlay; import com.velocitypowered.api.util.bossbar.BossBarOverlay;
import com.velocitypowered.proxy.command.GlistCommand;
import com.velocitypowered.proxy.command.ServerCommand;
import com.velocitypowered.proxy.command.ShutdownCommand;
import com.velocitypowered.proxy.command.VelocityCommand;
import com.velocitypowered.proxy.command.VelocityCommandManager; import com.velocitypowered.proxy.command.VelocityCommandManager;
import com.velocitypowered.proxy.command.builtin.GlistCommand;
import com.velocitypowered.proxy.command.builtin.ServerCommand;
import com.velocitypowered.proxy.command.builtin.ShutdownCommand;
import com.velocitypowered.proxy.command.builtin.VelocityCommand;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
@ -194,7 +194,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
commandManager.register("velocity", new VelocityCommand(this)); commandManager.register("velocity", new VelocityCommand(this));
commandManager.register("server", new ServerCommand(this)); commandManager.register("server", new ServerCommand(this));
commandManager.register("shutdown", new ShutdownCommand(this),"end"); commandManager.register("shutdown", new ShutdownCommand(this),"end");
commandManager.register("glist", new GlistCommand(this)); new GlistCommand(this).register();
try { try {
Path configPath = Paths.get("velocity.toml"); Path configPath = Paths.get("velocity.toml");

Datei anzeigen

@ -0,0 +1,31 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.command.CommandInvocation;
import com.velocitypowered.api.command.CommandSource;
/**
* Abstract base class for {@link CommandInvocation} implementations.
*
* @param <T> the type of the arguments
*/
abstract class AbstractCommandInvocation<T> implements CommandInvocation<T> {
private final CommandSource source;
private final T arguments;
protected AbstractCommandInvocation(final CommandSource source, final T arguments) {
this.source = Preconditions.checkNotNull(source, "source");
this.arguments = Preconditions.checkNotNull(arguments, "arguments");
}
@Override
public CommandSource source() {
return source;
}
@Override
public T arguments() {
return arguments;
}
}

Datei anzeigen

@ -0,0 +1,23 @@
package com.velocitypowered.proxy.command;
import com.mojang.brigadier.context.CommandContext;
import com.velocitypowered.api.command.CommandInvocation;
import com.velocitypowered.api.command.CommandSource;
/**
* Creates command invocation contexts for the given {@link CommandSource}
* and command line arguments.
*
* @param <I> the type of the built invocation
*/
@FunctionalInterface
public interface CommandInvocationFactory<I extends CommandInvocation<?>> {
/**
* Returns an invocation context for the given Brigadier context.
*
* @param context the command context
* @return the built invocation context
*/
I create(final CommandContext<CommandSource> context);
}

Datei anzeigen

@ -0,0 +1,97 @@
package com.velocitypowered.proxy.command;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandInvocation;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.InvocableCommand;
import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.proxy.util.BrigadierUtils;
@FunctionalInterface
public interface CommandNodeFactory<T extends Command> {
InvocableCommandNodeFactory<SimpleCommand.Invocation> SIMPLE =
new InvocableCommandNodeFactory<>() {
@Override
protected SimpleCommand.Invocation createInvocation(
final CommandContext<CommandSource> context) {
return VelocitySimpleCommandInvocation.FACTORY.create(context);
}
};
InvocableCommandNodeFactory<RawCommand.Invocation> RAW =
new InvocableCommandNodeFactory<>() {
@Override
protected RawCommand.Invocation createInvocation(
final CommandContext<CommandSource> context) {
return VelocityRawCommandInvocation.FACTORY.create(context);
}
};
CommandNodeFactory<Command> FALLBACK = (alias, command) ->
BrigadierUtils.buildRawArgumentsLiteral(alias,
context -> {
CommandSource source = context.getSource();
String[] args = BrigadierUtils.getSplitArguments(context);
if (!command.hasPermission(source, args)) {
return BrigadierCommand.FORWARD;
}
command.execute(source, args);
return 1;
},
(context, builder) -> {
String[] args = BrigadierUtils.getSplitArguments(context);
return command.suggestAsync(context.getSource(), args).thenApply(values -> {
for (String value : values) {
builder.suggest(value);
}
return builder.build();
});
});
/**
* Returns a Brigadier node for the execution of the given command.
*
* @param alias the command alias
* @param command the command to execute
* @return the command node
*/
LiteralCommandNode<CommandSource> create(String alias, T command);
abstract class InvocableCommandNodeFactory<I extends CommandInvocation<?>>
implements CommandNodeFactory<InvocableCommand<I>> {
@Override
public LiteralCommandNode<CommandSource> create(
final String alias, final InvocableCommand<I> command) {
return BrigadierUtils.buildRawArgumentsLiteral(alias,
context -> {
I invocation = createInvocation(context);
if (!command.hasPermission(invocation)) {
return BrigadierCommand.FORWARD;
}
command.execute(invocation);
return 1;
},
(context, builder) -> {
I invocation = createInvocation(context);
return command.suggestAsync(invocation).thenApply(values -> {
for (String value : values) {
builder.suggest(value);
}
return builder.build();
});
});
}
protected abstract I createInvocation(final CommandContext<CommandSource> context);
}
}

Datei anzeigen

@ -1,110 +0,0 @@
package com.velocitypowered.proxy.command;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
public class GlistCommand implements Command {
private final ProxyServer server;
public GlistCommand(ProxyServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length == 0) {
sendTotalProxyCount(source);
source.sendMessage(
TextComponent.builder("To view all players on servers, use ", NamedTextColor.YELLOW)
.append("/glist all", NamedTextColor.DARK_AQUA)
.append(".", NamedTextColor.YELLOW)
.build());
} else if (args.length == 1) {
String arg = args[0];
if (arg.equalsIgnoreCase("all")) {
for (RegisteredServer server : BuiltinCommandUtil.sortedServerList(server)) {
sendServerPlayers(source, server, true);
}
sendTotalProxyCount(source);
} else {
Optional<RegisteredServer> registeredServer = server.getServer(arg);
if (!registeredServer.isPresent()) {
source.sendMessage(
TextComponent.of("Server " + arg + " doesn't exist.", NamedTextColor.RED));
return;
}
sendServerPlayers(source, registeredServer.get(), false);
}
} else {
source.sendMessage(TextComponent.of("Too many arguments.", NamedTextColor.RED));
}
}
private void sendTotalProxyCount(CommandSource target) {
target.sendMessage(TextComponent.builder("There are ", NamedTextColor.YELLOW)
.append(Integer.toString(server.getAllPlayers().size()), NamedTextColor.GREEN)
.append(" player(s) online.", NamedTextColor.YELLOW)
.build());
}
private void sendServerPlayers(CommandSource target, RegisteredServer server, boolean fromAll) {
List<Player> onServer = ImmutableList.copyOf(server.getPlayersConnected());
if (onServer.isEmpty() && fromAll) {
return;
}
TextComponent.Builder builder = TextComponent.builder()
.append(TextComponent.of("[" + server.getServerInfo().getName() + "] ",
NamedTextColor.DARK_AQUA))
.append("(" + onServer.size() + ")", NamedTextColor.GRAY)
.append(": ")
.resetStyle();
for (int i = 0; i < onServer.size(); i++) {
Player player = onServer.get(i);
builder.append(player.getUsername());
if (i + 1 < onServer.size()) {
builder.append(", ");
}
}
target.sendMessage(builder.build());
}
@Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
ImmutableList.Builder<String> options = ImmutableList.builder();
for (RegisteredServer server : server.getAllServers()) {
options.add(server.getServerInfo().getName());
}
options.add("all");
switch (currentArgs.length) {
case 0:
return options.build();
case 1:
return options.build().stream()
.filter(o -> o.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
.collect(ImmutableList.toImmutableList());
default:
return ImmutableList.of();
}
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source.getPermissionValue("velocity.command.glist") == Tristate.TRUE;
}
}

Datei anzeigen

@ -1,31 +0,0 @@
package com.velocitypowered.proxy.command;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ShutdownCommand implements Command {
private final VelocityServer server;
public ShutdownCommand(VelocityServer server) {
this.server = server;
}
@Override
public void execute(CommandSource source, String @NonNull [] args) {
if (args.length == 0) {
server.shutdown(true);
} else {
String reason = String.join(" ", args);
server.shutdown(true, LegacyComponentSerializer.legacy('&').deserialize(reason));
}
}
@Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
return source == server.getConsoleCommandSource();
}
}

Datei anzeigen

@ -1,273 +1,229 @@
package com.velocitypowered.proxy.command; package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList; import com.google.common.collect.Lists;
import com.google.common.collect.ImmutableSet; import com.mojang.brigadier.CommandDispatcher;
import com.mojang.brigadier.ParseResults;
import com.mojang.brigadier.exceptions.CommandSyntaxException;
import com.mojang.brigadier.suggestion.Suggestion;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.Command; import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandManager; import com.velocitypowered.api.command.CommandManager;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.RawCommand; import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.event.command.CommandExecuteEvent; import com.velocitypowered.api.event.command.CommandExecuteEvent;
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult; import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
import com.velocitypowered.proxy.plugin.VelocityEventManager; import com.velocitypowered.proxy.plugin.VelocityEventManager;
import com.velocitypowered.proxy.util.BrigadierUtils;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Locale; import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
public class VelocityCommandManager implements CommandManager { public class VelocityCommandManager implements CommandManager {
private final Map<String, RawCommand> commands = new HashMap<>(); private final CommandDispatcher<CommandSource> dispatcher;
private final VelocityEventManager eventManager; private final VelocityEventManager eventManager;
public VelocityCommandManager(VelocityEventManager eventManager) { public VelocityCommandManager(final VelocityEventManager eventManager) {
this.eventManager = eventManager; this.eventManager = Preconditions.checkNotNull(eventManager);
this.dispatcher = new CommandDispatcher<>();
}
@Override
public CommandMeta.Builder metaBuilder(final String alias) {
Preconditions.checkNotNull(alias, "alias");
return new VelocityCommandMeta.Builder(alias);
}
@Override
public CommandMeta.Builder metaBuilder(final BrigadierCommand command) {
Preconditions.checkNotNull(command, "command");
return new VelocityCommandMeta.Builder(command.getNode().getName());
} }
@Override @Override
@Deprecated
public void register(final Command command, final String... aliases) { public void register(final Command command, final String... aliases) {
Preconditions.checkArgument(aliases.length > 0, "no aliases provided"); Preconditions.checkArgument(aliases.length > 0, "no aliases provided");
register(aliases[0], command, Arrays.copyOfRange(aliases, 1, aliases.length)); register(aliases[0], command, Arrays.copyOfRange(aliases, 1, aliases.length));
} }
@Override @Override
public void register(String alias, Command command, String... otherAliases) { public void register(final String alias, final Command command, final String... otherAliases) {
Preconditions.checkNotNull(alias, "alias"); Preconditions.checkNotNull(alias, "alias");
Preconditions.checkNotNull(command, "command");
Preconditions.checkNotNull(otherAliases, "otherAliases"); Preconditions.checkNotNull(otherAliases, "otherAliases");
Preconditions.checkNotNull(command, "executor"); Preconditions.checkArgument(!hasCommand(alias), "alias already registered");
register(metaBuilder(alias).aliases(otherAliases).build(), command);
}
RawCommand rawCmd = RegularCommandWrapper.wrap(command); @Override
this.commands.put(alias.toLowerCase(Locale.ENGLISH), rawCmd); public void register(final BrigadierCommand command) {
Preconditions.checkNotNull(command, "command");
register(metaBuilder(command).build(), command);
}
for (int i = 0, length = otherAliases.length; i < length; i++) { @Override
final String alias1 = otherAliases[i]; public void register(final CommandMeta meta, final Command command) {
Preconditions.checkNotNull(alias1, "alias at index %s", i + 1); Preconditions.checkNotNull(meta, "meta");
this.commands.put(alias1.toLowerCase(Locale.ENGLISH), rawCmd); Preconditions.checkNotNull(command, "command");
Iterator<String> aliasIterator = meta.getAliases().iterator();
String alias = aliasIterator.next();
LiteralCommandNode<CommandSource> node = null;
if (command instanceof BrigadierCommand) {
node = ((BrigadierCommand) command).getNode();
} else if (command instanceof SimpleCommand) {
node = CommandNodeFactory.SIMPLE.create(alias, (SimpleCommand) command);
} else if (command instanceof RawCommand) {
// This ugly hack will be removed in Velocity 2.0. Most if not all plugins
// have side-effect free #suggest methods. We rely on the newer RawCommand
// throwing UOE.
RawCommand asRaw = (RawCommand) command;
try {
asRaw.suggest(null, new String[0]);
} catch (final UnsupportedOperationException e) {
node = CommandNodeFactory.RAW.create(alias, asRaw);
} catch (final Exception ignored) {
// The implementation probably relies on a non-null source
}
}
if (node == null) {
node = CommandNodeFactory.FALLBACK.create(alias, command);
}
if (!(command instanceof BrigadierCommand)) {
for (CommandNode<CommandSource> hint : meta.getHints()) {
node.addChild(BrigadierUtils.wrapForHinting(hint, node.getCommand()));
}
}
dispatcher.getRoot().addChild(node);
while (aliasIterator.hasNext()) {
String otherAlias = aliasIterator.next();
Preconditions.checkArgument(!hasCommand(otherAlias),
"alias %s is already registered", otherAlias);
dispatcher.getRoot().addChild(BrigadierUtils.buildRedirect(otherAlias, node));
} }
} }
@Override @Override
public void unregister(final String alias) { public void unregister(final String alias) {
Preconditions.checkNotNull(alias, "name"); Preconditions.checkNotNull(alias, "alias");
this.commands.remove(alias.toLowerCase(Locale.ENGLISH)); CommandNode<CommandSource> node =
dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH));
if (node != null) {
dispatcher.getRoot().getChildren().remove(node);
}
} }
/** /**
* Calls CommandExecuteEvent. * Fires a {@link CommandExecuteEvent}.
* @param source the command's source *
* @param cmd the command * @param source the source to execute the command for
* @return CompletableFuture of event * @param cmdLine the command to execute
* @return the {@link CompletableFuture} of the event
*/ */
public CompletableFuture<CommandExecuteEvent> callCommandEvent(CommandSource source, String cmd) { public CompletableFuture<CommandExecuteEvent> callCommandEvent(final CommandSource source,
final String cmdLine) {
Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmd, "cmd"); Preconditions.checkNotNull(cmdLine, "cmdLine");
return eventManager.fire(new CommandExecuteEvent(source, cmd)); return eventManager.fire(new CommandExecuteEvent(source, cmdLine));
} }
@Override @Override
public boolean execute(CommandSource source, String cmdLine) { public boolean execute(final CommandSource source, final String cmdLine) {
Preconditions.checkNotNull(source, "source"); return executeAsync(source, cmdLine).join();
Preconditions.checkNotNull(cmdLine, "cmdLine");
CommandExecuteEvent event = callCommandEvent(source, cmdLine).join();
CommandResult commandResult = event.getResult();
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
return false;
}
cmdLine = commandResult.getCommand().orElse(event.getCommand());
return executeImmediately(source, cmdLine);
} }
@Override @Override
public boolean executeImmediately(CommandSource source, String cmdLine) { public boolean executeImmediately(final CommandSource source, final String cmdLine) {
Preconditions.checkNotNull(source, "source"); Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine"); Preconditions.checkNotNull(cmdLine, "cmdLine");
String alias = cmdLine; ParseResults<CommandSource> results = parse(cmdLine, source, true);
String args = "";
int firstSpace = cmdLine.indexOf(' ');
if (firstSpace != -1) {
alias = cmdLine.substring(0, firstSpace);
args = cmdLine.substring(firstSpace);
}
RawCommand command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
return false;
}
try { try {
if (!command.hasPermission(source, args)) { return dispatcher.execute(results) != BrigadierCommand.FORWARD;
return false; } catch (final CommandSyntaxException e) {
boolean isSyntaxError = !e.getType().equals(
CommandSyntaxException.BUILT_IN_EXCEPTIONS.dispatcherUnknownCommand());
if (isSyntaxError) {
source.sendMessage(TextComponent.of(e.getMessage(), NamedTextColor.RED));
} }
command.execute(source, args); return false;
return true; } catch (final Exception e) {
} catch (Exception e) {
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e); throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
} }
} }
@Override @Override
public CompletableFuture<Boolean> executeAsync(CommandSource source, String cmdLine) { public CompletableFuture<Boolean> executeAsync(final CommandSource source, final String cmdLine) {
CompletableFuture<Boolean> result = new CompletableFuture<>(); Preconditions.checkNotNull(source, "source");
callCommandEvent(source, cmdLine).thenAccept(event -> { Preconditions.checkNotNull(cmdLine, "cmdLine");
return callCommandEvent(source, cmdLine).thenApply(event -> {
CommandResult commandResult = event.getResult(); CommandResult commandResult = event.getResult();
if (commandResult.isForwardToServer() || !commandResult.isAllowed()) { if (commandResult.isForwardToServer() || !commandResult.isAllowed()) {
result.complete(false);
}
String command = commandResult.getCommand().orElse(event.getCommand());
try {
result.complete(executeImmediately(source, command));
} catch (Exception e) {
result.completeExceptionally(e);
}
});
return result;
}
@Override
public CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
CompletableFuture<Boolean> result = new CompletableFuture<>();
eventManager.getService().execute(() -> {
try {
result.complete(executeImmediately(source, cmdLine));
} catch (Exception e) {
result.completeExceptionally(e);
}
});
return result;
}
public boolean hasCommand(String command) {
return commands.containsKey(command);
}
public Set<String> getAllRegisteredCommands() {
return ImmutableSet.copyOf(commands.keySet());
}
/**
* Offer suggestions to fill in the command.
* @param source the source for the command
* @param cmdLine the partially completed command
* @return a {@link CompletableFuture} eventually completed with a {@link List}, possibly empty
*/
public CompletableFuture<List<String>> offerSuggestions(CommandSource source, String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
int firstSpace = cmdLine.indexOf(' ');
if (firstSpace == -1) {
// Offer to fill in commands.
ImmutableList.Builder<String> availableCommands = ImmutableList.builder();
for (Map.Entry<String, RawCommand> entry : commands.entrySet()) {
if (entry.getKey().regionMatches(true, 0, cmdLine, 0, cmdLine.length())
&& entry.getValue().hasPermission(source, new String[0])) {
availableCommands.add("/" + entry.getKey());
}
}
return CompletableFuture.completedFuture(availableCommands.build());
}
String alias = cmdLine.substring(0, firstSpace);
String args = cmdLine.substring(firstSpace);
RawCommand command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
// No such command, so we can't offer any tab complete suggestions.
return CompletableFuture.completedFuture(ImmutableList.of());
}
try {
if (!command.hasPermission(source, args)) {
return CompletableFuture.completedFuture(ImmutableList.of());
}
return command.suggest(source, args)
.thenApply(ImmutableList::copyOf);
} catch (Exception e) {
throw new RuntimeException(
"Unable to invoke suggestions for command " + cmdLine + " for " + source, e);
}
}
/**
* Determines if the {@code source} has permission to run the {@code cmdLine}.
* @param source the source to check against
* @param cmdLine the command to run
* @return {@code true} if the command can be run, otherwise {@code false}
*/
public boolean hasPermission(CommandSource source, String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
String alias = cmdLine;
String args = "";
int firstSpace = cmdLine.indexOf(' ');
if (firstSpace != -1) {
alias = cmdLine.substring(0, firstSpace);
args = cmdLine.substring(firstSpace).trim();
}
RawCommand command = commands.get(alias.toLowerCase(Locale.ENGLISH));
if (command == null) {
return false; return false;
} }
return executeImmediately(source, commandResult.getCommand().orElse(event.getCommand()));
try { });
return command.hasPermission(source, args);
} catch (Exception e) {
throw new RuntimeException(
"Unable to invoke suggestions for command " + alias + " for " + source, e);
}
}
private static class RegularCommandWrapper implements RawCommand {
private final Command delegate;
private RegularCommandWrapper(Command delegate) {
this.delegate = delegate;
}
private static String[] split(String line) {
if (line.isEmpty()) {
return new String[0];
}
String[] trimmed = line.trim().split(" ", -1);
if (line.endsWith(" ") && !line.trim().isEmpty()) {
// To work around a 1.13+ specific bug we have to inject a space at the end of the arguments
trimmed = Arrays.copyOf(trimmed, trimmed.length + 1);
trimmed[trimmed.length - 1] = "";
}
return trimmed;
} }
@Override @Override
public void execute(CommandSource source, String commandLine) { public CompletableFuture<Boolean> executeImmediatelyAsync(
delegate.execute(source, split(commandLine)); final CommandSource source, final String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
return CompletableFuture.supplyAsync(
() -> executeImmediately(source, cmdLine), eventManager.getService());
} }
@Override /**
public CompletableFuture<List<String>> suggest(CommandSource source, String currentLine) { * Returns suggestions to fill in the given command.
return delegate.suggestAsync(source, split(currentLine)); *
* @param source the source to execute the command for
* @param cmdLine the partially completed command
* @return a {@link CompletableFuture} eventually completed with a {@link List},
* possibly empty
*/
public CompletableFuture<List<String>> offerSuggestions(final CommandSource source,
final String cmdLine) {
Preconditions.checkNotNull(source, "source");
Preconditions.checkNotNull(cmdLine, "cmdLine");
ParseResults<CommandSource> parse = parse(cmdLine, source, false);
return dispatcher.getCompletionSuggestions(parse)
.thenApply(suggestions -> Lists.transform(suggestions.getList(), Suggestion::getText));
} }
@Override private ParseResults<CommandSource> parse(final String cmdLine, final CommandSource source,
public boolean hasPermission(CommandSource source, String commandLine) { final boolean trim) {
return delegate.hasPermission(source, split(commandLine)); String normalized = BrigadierUtils.normalizeInput(cmdLine, trim);
return dispatcher.parse(normalized, source);
} }
static RawCommand wrap(Command command) { /**
if (command instanceof RawCommand) { * Returns whether the given alias is registered on this manager.
return (RawCommand) command; *
} * @param alias the command alias to check
return new RegularCommandWrapper(command); * @return {@code true} if the alias is registered
*/
public boolean hasCommand(final String alias) {
Preconditions.checkNotNull(alias, "alias");
return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH)) != null;
} }
public CommandDispatcher<CommandSource> getDispatcher() {
return dispatcher;
} }
} }

Datei anzeigen

@ -0,0 +1,70 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.mojang.brigadier.tree.CommandNode;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
import java.util.Set;
final class VelocityCommandMeta implements CommandMeta {
static final class Builder implements CommandMeta.Builder {
private final ImmutableSet.Builder<String> aliases;
private final ImmutableList.Builder<CommandNode<CommandSource>> hints;
public Builder(final String alias) {
Preconditions.checkNotNull(alias, "alias");
this.aliases = ImmutableSet.<String>builder()
.add(alias.toLowerCase(Locale.ENGLISH));
this.hints = ImmutableList.builder();
}
@Override
public CommandMeta.Builder aliases(final String... aliases) {
Preconditions.checkNotNull(aliases, "aliases");
for (int i = 0, length = aliases.length; i < length; i++) {
final String alias1 = aliases[i];
Preconditions.checkNotNull(alias1, "alias at index %s", i);
this.aliases.add(alias1.toLowerCase(Locale.ENGLISH));
}
return this;
}
@Override
public CommandMeta.Builder hint(final CommandNode<CommandSource> node) {
Preconditions.checkNotNull(node, "node");
hints.add(node);
return this;
}
@Override
public CommandMeta build() {
return new VelocityCommandMeta(aliases.build(), hints.build());
}
}
private final Set<String> aliases;
private final List<CommandNode<CommandSource>> hints;
private VelocityCommandMeta(
final Set<String> aliases, final List<CommandNode<CommandSource>> hints) {
this.aliases = aliases;
this.hints = hints;
}
@Override
public Collection<String> getAliases() {
return aliases;
}
@Override
public Collection<CommandNode<CommandSource>> getHints() {
return hints;
}
}

Datei anzeigen

@ -0,0 +1,37 @@
package com.velocitypowered.proxy.command;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.context.CommandContext;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.proxy.util.BrigadierUtils;
final class VelocityRawCommandInvocation extends AbstractCommandInvocation<String>
implements RawCommand.Invocation {
static final Factory FACTORY = new Factory();
static class Factory implements CommandInvocationFactory<RawCommand.Invocation> {
@Override
public RawCommand.Invocation create(final CommandContext<CommandSource> context) {
return new VelocityRawCommandInvocation(
context.getSource(),
BrigadierUtils.getAlias(context),
BrigadierUtils.getRawArguments(context));
}
}
private final String alias;
private VelocityRawCommandInvocation(final CommandSource source,
final String alias, final String arguments) {
super(source, arguments);
this.alias = Preconditions.checkNotNull(alias);
}
@Override
public String alias() {
return alias;
}
}

Datei anzeigen

@ -0,0 +1,25 @@
package com.velocitypowered.proxy.command;
import com.mojang.brigadier.context.CommandContext;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.proxy.util.BrigadierUtils;
final class VelocitySimpleCommandInvocation extends AbstractCommandInvocation<String[]>
implements SimpleCommand.Invocation {
static final Factory FACTORY = new Factory();
static class Factory implements CommandInvocationFactory<SimpleCommand.Invocation> {
@Override
public SimpleCommand.Invocation create(final CommandContext<CommandSource> context) {
final String[] arguments = BrigadierUtils.getSplitArguments(context);
return new VelocitySimpleCommandInvocation(context.getSource(), arguments);
}
}
VelocitySimpleCommandInvocation(final CommandSource source, final String[] arguments) {
super(source, arguments);
}
}

Datei anzeigen

@ -1,4 +1,4 @@
package com.velocitypowered.proxy.command; package com.velocitypowered.proxy.command.builtin;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer; import com.velocitypowered.api.proxy.server.RegisteredServer;

Datei anzeigen

@ -0,0 +1,120 @@
package com.velocitypowered.proxy.command.builtin;
import static com.mojang.brigadier.arguments.StringArgumentType.getString;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.util.List;
import java.util.Optional;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.format.NamedTextColor;
public class GlistCommand {
private static final String SERVER_ARG = "server";
private final ProxyServer server;
public GlistCommand(ProxyServer server) {
this.server = server;
}
/**
* Registers this command.
*/
public void register() {
LiteralCommandNode<CommandSource> totalNode = LiteralArgumentBuilder
.<CommandSource>literal("glist")
.requires(source ->
source.getPermissionValue("velocity.command.glist") == Tristate.TRUE)
.executes(this::totalCount)
.build();
ArgumentCommandNode<CommandSource, String> serverNode = RequiredArgumentBuilder
.<CommandSource, String>argument(SERVER_ARG, StringArgumentType.string())
.suggests((context, builder) -> {
for (RegisteredServer server : server.getAllServers()) {
builder.suggest(server.getServerInfo().getName());
}
builder.suggest("all");
return builder.buildFuture();
})
.executes(this::serverCount)
.build();
totalNode.addChild(serverNode);
server.getCommandManager().register(new BrigadierCommand(totalNode));
}
private int totalCount(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
sendTotalProxyCount(source);
source.sendMessage(
TextComponent.builder("To view all players on servers, use ", NamedTextColor.YELLOW)
.append("/glist all", NamedTextColor.DARK_AQUA)
.append(".", NamedTextColor.YELLOW)
.build());
return 1;
}
private int serverCount(final CommandContext<CommandSource> context) {
final CommandSource source = context.getSource();
final String serverName = getString(context, SERVER_ARG);
if (serverName.equalsIgnoreCase("all")) {
for (RegisteredServer server : BuiltinCommandUtil.sortedServerList(server)) {
sendServerPlayers(source, server, true);
}
sendTotalProxyCount(source);
} else {
Optional<RegisteredServer> registeredServer = server.getServer(serverName);
if (!registeredServer.isPresent()) {
source.sendMessage(
TextComponent.of("Server " + serverName + " doesn't exist.", NamedTextColor.RED));
return -1;
}
sendServerPlayers(source, registeredServer.get(), false);
}
return 1;
}
private void sendTotalProxyCount(CommandSource target) {
target.sendMessage(TextComponent.builder("There are ", NamedTextColor.YELLOW)
.append(Integer.toString(server.getAllPlayers().size()), NamedTextColor.GREEN)
.append(" player(s) online.", NamedTextColor.YELLOW)
.build());
}
private void sendServerPlayers(CommandSource target, RegisteredServer server, boolean fromAll) {
List<Player> onServer = ImmutableList.copyOf(server.getPlayersConnected());
if (onServer.isEmpty() && fromAll) {
return;
}
TextComponent.Builder builder = TextComponent.builder()
.append(TextComponent.of("[" + server.getServerInfo().getName() + "] ",
NamedTextColor.DARK_AQUA))
.append("(" + onServer.size() + ")", NamedTextColor.GRAY)
.append(": ")
.resetStyle();
for (int i = 0; i < onServer.size(); i++) {
Player player = onServer.get(i);
builder.append(player.getUsername());
if (i + 1 < onServer.size()) {
builder.append(", ");
}
}
target.sendMessage(builder.build());
}
}

Datei anzeigen

@ -1,10 +1,10 @@
package com.velocitypowered.proxy.command; package com.velocitypowered.proxy.command.builtin;
import static net.kyori.adventure.text.event.HoverEvent.showText; import static net.kyori.adventure.text.event.HoverEvent.showText;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.proxy.Player; import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer; import com.velocitypowered.api.proxy.ProxyServer;
@ -18,9 +18,8 @@ import java.util.stream.Stream;
import net.kyori.adventure.text.TextComponent; import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.event.ClickEvent; import net.kyori.adventure.text.event.ClickEvent;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import org.checkerframework.checker.nullness.qual.NonNull;
public class ServerCommand implements Command { public class ServerCommand implements SimpleCommand {
public static final int MAX_SERVERS_TO_LIST = 50; public static final int MAX_SERVERS_TO_LIST = 50;
private final ProxyServer server; private final ProxyServer server;
@ -30,7 +29,10 @@ public class ServerCommand implements Command {
} }
@Override @Override
public void execute(CommandSource source, String @NonNull [] args) { public void execute(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] args = invocation.arguments();
if (!(source instanceof Player)) { if (!(source instanceof Player)) {
source.sendMessage(TextComponent.of("Only players may run this command.", source.sendMessage(TextComponent.of("Only players may run this command.",
NamedTextColor.RED)); NamedTextColor.RED));
@ -102,9 +104,11 @@ public class ServerCommand implements Command {
} }
@Override @Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) { public List<String> suggest(final SimpleCommand.Invocation invocation) {
Stream<String> possibilities = Stream.concat(Stream.of("all"), server.getAllServers() final String[] currentArgs = invocation.arguments();
.stream().map(rs -> rs.getServerInfo().getName())); Stream<String> possibilities = server.getAllServers().stream()
.map(rs -> rs.getServerInfo().getName());
if (currentArgs.length == 0) { if (currentArgs.length == 0) {
return possibilities.collect(Collectors.toList()); return possibilities.collect(Collectors.toList());
} else if (currentArgs.length == 1) { } else if (currentArgs.length == 1) {
@ -117,7 +121,7 @@ public class ServerCommand implements Command {
} }
@Override @Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) { public boolean hasPermission(final SimpleCommand.Invocation invocation) {
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE; return invocation.source().getPermissionValue("velocity.command.server") != Tristate.FALSE;
} }
} }

Datei anzeigen

@ -0,0 +1,25 @@
package com.velocitypowered.proxy.command.builtin;
import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.proxy.VelocityServer;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
public class ShutdownCommand implements RawCommand {
private final VelocityServer server;
public ShutdownCommand(VelocityServer server) {
this.server = server;
}
@Override
public void execute(final Invocation invocation) {
String reason = invocation.arguments();
server.shutdown(true, LegacyComponentSerializer.legacy('&').deserialize(reason));
}
@Override
public boolean hasPermission(final Invocation invocation) {
return invocation.source() == server.getConsoleCommandSource();
}
}

Datei anzeigen

@ -1,10 +1,10 @@
package com.velocitypowered.proxy.command; package com.velocitypowered.proxy.command.builtin;
import com.google.common.base.Joiner; import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMap;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandSource; import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.api.permission.Tristate; import com.velocitypowered.api.permission.Tristate;
import com.velocitypowered.api.plugin.PluginContainer; import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginDescription; import com.velocitypowered.api.plugin.PluginDescription;
@ -25,16 +25,28 @@ import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger; import org.apache.logging.log4j.Logger;
import org.checkerframework.checker.nullness.qual.NonNull; import org.checkerframework.checker.nullness.qual.NonNull;
public class VelocityCommand implements Command { public class VelocityCommand implements SimpleCommand {
private final Map<String, Command> subcommands; private interface SubCommand {
void execute(final CommandSource source, final String @NonNull [] args);
default List<String> suggest(final CommandSource source, final String @NonNull [] currentArgs) {
return ImmutableList.of();
}
boolean hasPermission(final CommandSource source, final String @NonNull [] args);
}
private final Map<String, SubCommand> commands;
/** /**
* Initializes the command object for /velocity. * Initializes the command object for /velocity.
*
* @param server the Velocity server * @param server the Velocity server
*/ */
public VelocityCommand(VelocityServer server) { public VelocityCommand(VelocityServer server) {
this.subcommands = ImmutableMap.<String, Command>builder() this.commands = ImmutableMap.<String, SubCommand>builder()
.put("version", new Info(server)) .put("version", new Info(server))
.put("plugins", new Plugins(server)) .put("plugins", new Plugins(server))
.put("reload", new Reload(server)) .put("reload", new Reload(server))
@ -42,7 +54,7 @@ public class VelocityCommand implements Command {
} }
private void usage(CommandSource source) { private void usage(CommandSource source) {
String availableCommands = subcommands.entrySet().stream() String availableCommands = commands.entrySet().stream()
.filter(e -> e.getValue().hasPermission(source, new String[0])) .filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.collect(Collectors.joining("|")); .collect(Collectors.joining("|"));
@ -51,13 +63,16 @@ public class VelocityCommand implements Command {
} }
@Override @Override
public void execute(CommandSource source, String @NonNull [] args) { public void execute(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] args = invocation.arguments();
if (args.length == 0) { if (args.length == 0) {
usage(source); usage(source);
return; return;
} }
Command command = subcommands.get(args[0].toLowerCase(Locale.US)); SubCommand command = commands.get(args[0].toLowerCase(Locale.US));
if (command == null) { if (command == null) {
usage(source); usage(source);
return; return;
@ -68,16 +83,19 @@ public class VelocityCommand implements Command {
} }
@Override @Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) { public List<String> suggest(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] currentArgs = invocation.arguments();
if (currentArgs.length == 0) { if (currentArgs.length == 0) {
return subcommands.entrySet().stream() return commands.entrySet().stream()
.filter(e -> e.getValue().hasPermission(source, new String[0])) .filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey) .map(Map.Entry::getKey)
.collect(ImmutableList.toImmutableList()); .collect(ImmutableList.toImmutableList());
} }
if (currentArgs.length == 1) { if (currentArgs.length == 1) {
return subcommands.entrySet().stream() return commands.entrySet().stream()
.filter(e -> e.getKey().regionMatches(true, 0, currentArgs[0], 0, .filter(e -> e.getKey().regionMatches(true, 0, currentArgs[0], 0,
currentArgs[0].length())) currentArgs[0].length()))
.filter(e -> e.getValue().hasPermission(source, new String[0])) .filter(e -> e.getValue().hasPermission(source, new String[0]))
@ -85,7 +103,7 @@ public class VelocityCommand implements Command {
.collect(ImmutableList.toImmutableList()); .collect(ImmutableList.toImmutableList());
} }
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US)); SubCommand command = commands.get(currentArgs[0].toLowerCase(Locale.US));
if (command == null) { if (command == null) {
return ImmutableList.of(); return ImmutableList.of();
} }
@ -95,11 +113,14 @@ public class VelocityCommand implements Command {
} }
@Override @Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) { public boolean hasPermission(final SimpleCommand.Invocation invocation) {
final CommandSource source = invocation.source();
final String[] args = invocation.arguments();
if (args.length == 0) { if (args.length == 0) {
return subcommands.values().stream().anyMatch(e -> e.hasPermission(source, args)); return commands.values().stream().anyMatch(e -> e.hasPermission(source, args));
} }
Command command = subcommands.get(args[0].toLowerCase(Locale.US)); SubCommand command = commands.get(args[0].toLowerCase(Locale.US));
if (command == null) { if (command == null) {
return true; return true;
} }
@ -108,7 +129,7 @@ public class VelocityCommand implements Command {
return command.hasPermission(source, actualArgs); return command.hasPermission(source, actualArgs);
} }
private static class Reload implements Command { private static class Reload implements SubCommand {
private static final Logger logger = LogManager.getLogger(Reload.class); private static final Logger logger = LogManager.getLogger(Reload.class);
private final VelocityServer server; private final VelocityServer server;
@ -136,12 +157,12 @@ public class VelocityCommand implements Command {
} }
@Override @Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) { public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.reload") == Tristate.TRUE; return source.getPermissionValue("velocity.command.reload") == Tristate.TRUE;
} }
} }
private static class Info implements Command { private static class Info implements SubCommand {
private final ProxyServer server; private final ProxyServer server;
@ -189,12 +210,12 @@ public class VelocityCommand implements Command {
} }
@Override @Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) { public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE; return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
} }
} }
private static class Plugins implements Command { private static class Plugins implements SubCommand {
private final ProxyServer server; private final ProxyServer server;
@ -260,7 +281,7 @@ public class VelocityCommand implements Command {
} }
@Override @Override
public boolean hasPermission(CommandSource source, String @NonNull [] args) { public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE; return source.getPermissionValue("velocity.command.plugins") == Tristate.TRUE;
} }
} }

Datei anzeigen

@ -346,6 +346,10 @@ public class VelocityConfiguration implements ProxyConfig {
return advanced.isFailoverOnUnexpectedServerDisconnect(); return advanced.isFailoverOnUnexpectedServerDisconnect();
} }
public boolean isAnnounceProxyCommands() {
return advanced.isAnnounceProxyCommands();
}
public boolean isLogCommandExecutions() { public boolean isLogCommandExecutions() {
return advanced.isLogCommandExecutions(); return advanced.isLogCommandExecutions();
} }
@ -586,6 +590,7 @@ public class VelocityConfiguration implements ProxyConfig {
private boolean bungeePluginMessageChannel = true; private boolean bungeePluginMessageChannel = true;
private boolean showPingRequests = false; private boolean showPingRequests = false;
private boolean failoverOnUnexpectedServerDisconnect = true; private boolean failoverOnUnexpectedServerDisconnect = true;
private boolean announceProxyCommands = true;
private boolean logCommandExecutions = false; private boolean logCommandExecutions = false;
private Advanced() { private Advanced() {
@ -604,6 +609,7 @@ public class VelocityConfiguration implements ProxyConfig {
this.showPingRequests = config.getOrElse("show-ping-requests", false); this.showPingRequests = config.getOrElse("show-ping-requests", false);
this.failoverOnUnexpectedServerDisconnect = config this.failoverOnUnexpectedServerDisconnect = config
.getOrElse("failover-on-unexpected-server-disconnect", true); .getOrElse("failover-on-unexpected-server-disconnect", true);
this.announceProxyCommands = config.getOrElse("announce-proxy-commands", true);
this.logCommandExecutions = config.getOrElse("log-command-executions", false); this.logCommandExecutions = config.getOrElse("log-command-executions", false);
} }
} }
@ -648,6 +654,10 @@ public class VelocityConfiguration implements ProxyConfig {
return failoverOnUnexpectedServerDisconnect; return failoverOnUnexpectedServerDisconnect;
} }
public boolean isAnnounceProxyCommands() {
return announceProxyCommands;
}
public boolean isLogCommandExecutions() { public boolean isLogCommandExecutions() {
return logCommandExecutions; return logCommandExecutions;
} }
@ -665,6 +675,7 @@ public class VelocityConfiguration implements ProxyConfig {
+ ", bungeePluginMessageChannel=" + bungeePluginMessageChannel + ", bungeePluginMessageChannel=" + bungeePluginMessageChannel
+ ", showPingRequests=" + showPingRequests + ", showPingRequests=" + showPingRequests
+ ", failoverOnUnexpectedServerDisconnect=" + failoverOnUnexpectedServerDisconnect + ", failoverOnUnexpectedServerDisconnect=" + failoverOnUnexpectedServerDisconnect
+ ", announceProxyCommands=" + announceProxyCommands
+ ", logCommandExecutions=" + logCommandExecutions + ", logCommandExecutions=" + logCommandExecutions
+ '}'; + '}';
} }

Datei anzeigen

@ -3,10 +3,9 @@ package com.velocitypowered.proxy.connection.backend;
import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder.getBungeeCordChannel; import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder.getBungeeCordChannel;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.arguments.StringArgumentType; import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.builder.LiteralArgumentBuilder; import com.mojang.brigadier.tree.RootCommandNode;
import com.mojang.brigadier.builder.RequiredArgumentBuilder; import com.velocitypowered.api.command.CommandSource;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent; import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent; import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
@ -18,7 +17,6 @@ import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands; import com.velocitypowered.proxy.protocol.packet.AvailableCommands;
import com.velocitypowered.proxy.protocol.packet.AvailableCommands.ProtocolSuggestionProvider;
import com.velocitypowered.proxy.protocol.packet.BossBar; import com.velocitypowered.proxy.protocol.packet.BossBar;
import com.velocitypowered.proxy.protocol.packet.Disconnect; import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.KeepAlive; import com.velocitypowered.proxy.protocol.packet.KeepAlive;
@ -30,6 +28,7 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil; import io.netty.buffer.ByteBufUtil;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import io.netty.handler.timeout.ReadTimeoutException; import io.netty.handler.timeout.ReadTimeoutException;
import java.util.Collection;
public class BackendPlaySessionHandler implements MinecraftSessionHandler { public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@ -164,23 +163,18 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(AvailableCommands commands) { public boolean handle(AvailableCommands commands) {
RootCommandNode<CommandSource> rootNode = commands.getRootNode();
if (server.getConfiguration().isAnnounceProxyCommands()) {
// Inject commands from the proxy. // Inject commands from the proxy.
for (String command : server.getCommandManager().getAllRegisteredCommands()) { Collection<CommandNode<CommandSource>> proxyNodes = server.getCommandManager().getDispatcher()
if (!server.getCommandManager().hasPermission(serverConn.getPlayer(), command)) { .getRoot().getChildren();
continue; for (CommandNode<CommandSource> node : proxyNodes) {
rootNode.addChild(node);
} }
LiteralCommandNode<Object> root = LiteralArgumentBuilder.literal(command)
.then(RequiredArgumentBuilder.argument("args", StringArgumentType.greedyString())
.suggests(new ProtocolSuggestionProvider("minecraft:ask_server"))
.build())
.executes((ctx) -> 0)
.build();
commands.getRootNode().addChild(root);
} }
server.getEventManager().fire( server.getEventManager().fire(
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), commands.getRootNode())) new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode))
.thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop()); .thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop());
return true; return true;
} }

Datei anzeigen

@ -130,7 +130,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
server.getCommandManager().callCommandEvent(player, msg.substring(1)) server.getCommandManager().callCommandEvent(player, msg.substring(1))
.thenComposeAsync(event -> processCommandExecuteResult(originalCommand, .thenComposeAsync(event -> processCommandExecuteResult(originalCommand,
event.getResult())) event.getResult()))
.whenCompleteAsync((ignored, throwable) -> { .whenComplete((ignored, throwable) -> {
if (server.getConfiguration().isLogCommandExecutions()) { if (server.getConfiguration().isLogCommandExecutions()) {
logger.info("{} -> executed command /{}", player, originalCommand); logger.info("{} -> executed command /{}", player, originalCommand);
} }
@ -414,7 +414,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
for (String suggestion : suggestions) { for (String suggestion : suggestions) {
offers.add(new Offer(suggestion)); offers.add(new Offer(suggestion));
} }
int startPos = packet.getCommand().lastIndexOf(' ') + 1; int startPos = packet.getCommand().lastIndexOf(' ') + 1;
if (startPos > 0) { if (startPos > 0) {
TabCompleteResponse resp = new TabCompleteResponse(); TabCompleteResponse resp = new TabCompleteResponse();
@ -460,9 +459,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
String command = request.getCommand().substring(1); String command = request.getCommand().substring(1);
server.getCommandManager().offerSuggestions(player, command) server.getCommandManager().offerSuggestions(player, command)
.thenAcceptAsync(offers -> { .thenAcceptAsync(offers -> {
boolean needsSlash = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
try { try {
for (String offer : offers) { for (String offer : offers) {
response.getOffers().add(new Offer(offer, null)); response.getOffers().add(new Offer(needsSlash ? "/" + offer : offer, null));
} }
response.getOffers().sort(null); response.getOffers().sort(null);
player.getConnection().write(response); player.getConnection().write(response);

Datei anzeigen

@ -71,17 +71,12 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
.appName("Velocity") .appName("Velocity")
.completer((reader, parsedLine, list) -> { .completer((reader, parsedLine, list) -> {
try { try {
boolean isCommand = parsedLine.line().indexOf(' ') == -1;
List<String> offers = this.server.getCommandManager() List<String> offers = this.server.getCommandManager()
.offerSuggestions(this, parsedLine.line()) .offerSuggestions(this, parsedLine.line())
.join(); // Console doesn't get harmed much by this... .join(); // Console doesn't get harmed much by this...
for (String offer : offers) { for (String offer : offers) {
if (isCommand) {
list.add(new Candidate(offer.substring(1)));
} else {
list.add(new Candidate(offer)); list.add(new Candidate(offer));
} }
}
} catch (Exception e) { } catch (Exception e) {
logger.error("An error occurred while trying to perform tab completion.", e); logger.error("An error occurred while trying to perform tab completion.", e);
} }

Datei anzeigen

@ -16,6 +16,7 @@ import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode; 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 com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
@ -44,14 +45,13 @@ public class AvailableCommands implements MinecraftPacket {
private static final byte FLAG_IS_REDIRECT = 0x08; private static final byte FLAG_IS_REDIRECT = 0x08;
private static final byte FLAG_HAS_SUGGESTIONS = 0x10; private static final byte FLAG_HAS_SUGGESTIONS = 0x10;
// Note: Velocity doesn't use Brigadier for command handling. This may change in Velocity 2.0.0. private @MonotonicNonNull RootCommandNode<CommandSource> rootNode;
private @MonotonicNonNull RootCommandNode<Object> rootNode;
/** /**
* Returns the root node. * Returns the root node.
* @return the root node * @return the root node
*/ */
public RootCommandNode<Object> getRootNode() { public RootCommandNode<CommandSource> getRootNode() {
if (rootNode == null) { if (rootNode == null) {
throw new IllegalStateException("Packet not yet deserialized"); throw new IllegalStateException("Packet not yet deserialized");
} }
@ -87,16 +87,16 @@ public class AvailableCommands implements MinecraftPacket {
} }
int rootIdx = ProtocolUtils.readVarInt(buf); int rootIdx = ProtocolUtils.readVarInt(buf);
rootNode = (RootCommandNode<Object>) wireNodes[rootIdx].built; rootNode = (RootCommandNode<CommandSource>) wireNodes[rootIdx].built;
} }
@Override @Override
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) { public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
// Assign all the children an index. // Assign all the children an index.
Deque<CommandNode<Object>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode)); Deque<CommandNode<CommandSource>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode));
Object2IntMap<CommandNode<Object>> idMappings = new Object2IntLinkedOpenHashMap<>(); Object2IntMap<CommandNode<CommandSource>> idMappings = new Object2IntLinkedOpenHashMap<>();
while (!childrenQueue.isEmpty()) { while (!childrenQueue.isEmpty()) {
CommandNode<Object> child = childrenQueue.poll(); CommandNode<CommandSource> child = childrenQueue.poll();
if (!idMappings.containsKey(child)) { if (!idMappings.containsKey(child)) {
idMappings.put(child, idMappings.size()); idMappings.put(child, idMappings.size());
childrenQueue.addAll(child.getChildren()); childrenQueue.addAll(child.getChildren());
@ -105,14 +105,14 @@ public class AvailableCommands implements MinecraftPacket {
// Now serialize the children. // Now serialize the children.
ProtocolUtils.writeVarInt(buf, idMappings.size()); ProtocolUtils.writeVarInt(buf, idMappings.size());
for (CommandNode<Object> child : idMappings.keySet()) { for (CommandNode<CommandSource> child : idMappings.keySet()) {
serializeNode(child, buf, idMappings); serializeNode(child, buf, idMappings);
} }
ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode)); ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode));
} }
private static void serializeNode(CommandNode<Object> node, ByteBuf buf, private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf,
Object2IntMap<CommandNode<Object>> idMappings) { Object2IntMap<CommandNode<CommandSource>> idMappings) {
byte flags = 0; byte flags = 0;
if (node.getRedirect() != null) { if (node.getRedirect() != null) {
flags |= FLAG_IS_REDIRECT; flags |= FLAG_IS_REDIRECT;
@ -127,7 +127,7 @@ public class AvailableCommands implements MinecraftPacket {
flags |= NODE_TYPE_LITERAL; flags |= NODE_TYPE_LITERAL;
} else if (node instanceof ArgumentCommandNode<?, ?>) { } else if (node instanceof ArgumentCommandNode<?, ?>) {
flags |= NODE_TYPE_ARGUMENT; flags |= NODE_TYPE_ARGUMENT;
if (((ArgumentCommandNode) node).getCustomSuggestions() != null) { if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
flags |= FLAG_HAS_SUGGESTIONS; flags |= FLAG_HAS_SUGGESTIONS;
} }
} else { } else {
@ -136,7 +136,7 @@ public class AvailableCommands implements MinecraftPacket {
buf.writeByte(flags); buf.writeByte(flags);
ProtocolUtils.writeVarInt(buf, node.getChildren().size()); ProtocolUtils.writeVarInt(buf, node.getChildren().size());
for (CommandNode<Object> child : node.getChildren()) { for (CommandNode<CommandSource> child : node.getChildren()) {
ProtocolUtils.writeVarInt(buf, idMappings.getInt(child)); ProtocolUtils.writeVarInt(buf, idMappings.getInt(child));
} }
if (node.getRedirect() != null) { if (node.getRedirect() != null) {
@ -145,20 +145,17 @@ public class AvailableCommands implements MinecraftPacket {
if (node instanceof ArgumentCommandNode<?, ?>) { if (node instanceof ArgumentCommandNode<?, ?>) {
ProtocolUtils.writeString(buf, node.getName()); ProtocolUtils.writeString(buf, node.getName());
ArgumentPropertyRegistry.serialize(buf, ((ArgumentCommandNode) node).getType()); ArgumentPropertyRegistry.serialize(buf,
((ArgumentCommandNode<CommandSource, ?>) node).getType());
if (((ArgumentCommandNode) node).getCustomSuggestions() != null) { if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
// The unchecked cast is required, but it is not particularly relevant because we check for SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node)
// a more specific type later. (Even then, we only pull out one field.) .getCustomSuggestions();
@SuppressWarnings("unchecked") String name = "minecraft:ask_server";
SuggestionProvider<Object> provider = ((ArgumentCommandNode) node).getCustomSuggestions(); if (provider instanceof ProtocolSuggestionProvider) {
name = ((ProtocolSuggestionProvider) provider).name;
if (!(provider instanceof ProtocolSuggestionProvider)) {
throw new IllegalArgumentException("Suggestion provider " + provider.getClass().getName()
+ " is not valid.");
} }
ProtocolUtils.writeString(buf, name);
ProtocolUtils.writeString(buf, ((ProtocolSuggestionProvider) provider).name);
} }
} else if (node instanceof LiteralCommandNode<?>) { } else if (node instanceof LiteralCommandNode<?>) {
ProtocolUtils.writeString(buf, node.getName()); ProtocolUtils.writeString(buf, node.getName());
@ -188,7 +185,7 @@ public class AvailableCommands implements MinecraftPacket {
String name = ProtocolUtils.readString(buf); String name = ProtocolUtils.readString(buf);
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf); ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf);
RequiredArgumentBuilder<Object, ?> argumentBuilder = RequiredArgumentBuilder RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder
.argument(name, argumentType); .argument(name, argumentType);
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) { if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf))); argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
@ -205,11 +202,11 @@ public class AvailableCommands implements MinecraftPacket {
private final byte flags; private final byte flags;
private final int[] children; private final int[] children;
private final int redirectTo; private final int redirectTo;
private final @Nullable ArgumentBuilder<Object, ?> args; private final @Nullable ArgumentBuilder<CommandSource, ?> args;
private @MonotonicNonNull CommandNode<Object> built; private @MonotonicNonNull CommandNode<CommandSource> built;
private WireNode(int idx, byte flags, int[] children, int redirectTo, private WireNode(int idx, byte flags, int[] children, int redirectTo,
@Nullable ArgumentBuilder<Object, ?> args) { @Nullable ArgumentBuilder<CommandSource, ?> args) {
this.idx = idx; this.idx = idx;
this.flags = flags; this.flags = flags;
this.children = children; this.children = children;
@ -251,7 +248,7 @@ public class AvailableCommands implements MinecraftPacket {
// If executable, add a dummy command // If executable, add a dummy command
if ((flags & FLAG_EXECUTABLE) != 0) { if ((flags & FLAG_EXECUTABLE) != 0) {
args.executes((Command<Object>) context -> 0); args.executes((Command<CommandSource>) context -> 0);
} }
this.built = args.build(); this.built = args.build();
@ -267,7 +264,7 @@ public class AvailableCommands implements MinecraftPacket {
// Associate children with nodes // Associate children with nodes
for (int child : children) { for (int child : children) {
CommandNode<Object> childNode = wireNodes[child].built; CommandNode<CommandSource> childNode = wireNodes[child].built;
if (!(childNode instanceof RootCommandNode)) { if (!(childNode instanceof RootCommandNode)) {
built.addChild(childNode); built.addChild(childNode);
} }
@ -286,9 +283,11 @@ public class AvailableCommands implements MinecraftPacket {
if (args != null) { if (args != null) {
if (args instanceof LiteralArgumentBuilder) { if (args instanceof LiteralArgumentBuilder) {
helper.add("argsLabel", ((LiteralArgumentBuilder) args).getLiteral()); helper.add("argsLabel",
((LiteralArgumentBuilder<CommandSource>) args).getLiteral());
} else if (args instanceof RequiredArgumentBuilder) { } else if (args instanceof RequiredArgumentBuilder) {
helper.add("argsName", ((RequiredArgumentBuilder) args).getName()); helper.add("argsName",
((RequiredArgumentBuilder<CommandSource, ?>) args).getName());
} }
} }
@ -300,7 +299,7 @@ public class AvailableCommands implements MinecraftPacket {
* A placeholder {@link SuggestionProvider} used internally to preserve the suggestion provider * A placeholder {@link SuggestionProvider} used internally to preserve the suggestion provider
* name. * name.
*/ */
public static class ProtocolSuggestionProvider implements SuggestionProvider<Object> { public static class ProtocolSuggestionProvider implements SuggestionProvider<CommandSource> {
private final String name; private final String name;
@ -309,7 +308,7 @@ public class AvailableCommands implements MinecraftPacket {
} }
@Override @Override
public CompletableFuture<Suggestions> getSuggestions(CommandContext<Object> context, public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSource> context,
SuggestionsBuilder builder) throws CommandSyntaxException { SuggestionsBuilder builder) throws CommandSyntaxException {
return builder.buildFuture(); return builder.buildFuture();
} }

Datei anzeigen

@ -0,0 +1,149 @@
package com.velocitypowered.proxy.util;
import com.google.common.base.Preconditions;
import com.mojang.brigadier.Command;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.ArgumentBuilder;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.context.CommandContext;
import com.mojang.brigadier.suggestion.SuggestionProvider;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.CommandSource;
import java.util.Locale;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Provides utilities for working with Brigadier commands.
*/
public final class BrigadierUtils {
/**
* Returns a literal node that redirects its execution to
* the given destination node.
*
* @param alias the command alias
* @param destination the destination node
* @return the built node
*/
public static LiteralCommandNode<CommandSource> buildRedirect(
final String alias, final LiteralCommandNode<CommandSource> destination) {
// Redirects only work for nodes with children, but break the top argument-less command.
// Manually adding the root command after setting the redirect doesn't fix it.
// See https://github.com/Mojang/brigadier/issues/46). Manually clone the node instead.
LiteralArgumentBuilder<CommandSource> builder = LiteralArgumentBuilder
.<CommandSource>literal(alias.toLowerCase(Locale.ENGLISH))
.requires(destination.getRequirement())
.forward(
destination.getRedirect(), destination.getRedirectModifier(), destination.isFork())
.executes(destination.getCommand());
for (CommandNode<CommandSource> child : destination.getChildren()) {
builder.then(child);
}
return builder.build();
}
/**
* Returns a literal node that optionally accepts arguments
* as a raw {@link String}.
*
* @param alias the literal alias
* @param brigadierCommand the command to execute
* @param suggestionProvider the suggestion provider
* @return the built node
*/
public static LiteralCommandNode<CommandSource> buildRawArgumentsLiteral(
final String alias, final Command<CommandSource> brigadierCommand,
SuggestionProvider<CommandSource> suggestionProvider) {
return LiteralArgumentBuilder
.<CommandSource>literal(alias.toLowerCase(Locale.ENGLISH))
.then(RequiredArgumentBuilder
.<CommandSource, String>argument("arguments", StringArgumentType.greedyString())
.suggests(suggestionProvider)
.executes(brigadierCommand))
.executes(brigadierCommand)
.build();
}
/**
* Returns the used command alias.
*
* @param context the command context
* @return the parsed command alias
*/
public static String getAlias(final CommandContext<CommandSource> context) {
return context.getNodes().get(0).getNode().getName();
}
/**
* Returns the raw {@link String} arguments of a command execution.
*
* @param context the command context
* @return the parsed arguments
*/
public static String getRawArguments(final CommandContext<CommandSource> context) {
String cmdLine = context.getInput();
int firstSpace = cmdLine.indexOf(' ');
if (firstSpace == -1) {
return "";
}
return cmdLine.substring(firstSpace + 1);
}
/**
* Returns the splitted arguments of a command node built with
* {@link #buildRawArgumentsLiteral(String, Command, SuggestionProvider)}.
*
* @param context the command context
* @return the parsed arguments
*/
public static String[] getSplitArguments(final CommandContext<CommandSource> context) {
String line = getRawArguments(context);
if (line.isEmpty()) {
return new String[0];
}
return line.trim().split(" ", -1);
}
/**
* Returns the normalized representation of the given command input.
*
* @param cmdLine the command input
* @param trim whether to trim argument-less inputs
* @return the normalized command
*/
public static String normalizeInput(final String cmdLine, final boolean trim) {
// Command aliases are case insensitive, but Brigadier isn't
String command = trim ? cmdLine.trim() : cmdLine;
int firstSpace = command.indexOf(' ');
if (firstSpace != -1) {
return command.substring(0, firstSpace).toLowerCase(Locale.ENGLISH)
+ command.substring(firstSpace);
}
return command.toLowerCase(Locale.ENGLISH);
}
/**
* Prepares the given command node prior for hinting metadata to
* a {@link com.velocitypowered.api.command.Command}.
*
* @param node the command node to be wrapped
* @param command the command to execute
* @return the wrapped command node
*/
public static CommandNode<CommandSource> wrapForHinting(
final CommandNode<CommandSource> node, final @Nullable Command<CommandSource> command) {
Preconditions.checkNotNull(node, "node");
ArgumentBuilder<CommandSource, ?> builder = node.createBuilder();
builder.executes(command);
for (CommandNode<CommandSource> child : node.getChildren()) {
builder.then(wrapForHinting(child, command));
}
return builder.build();
}
private BrigadierUtils() {
throw new AssertionError();
}
}

Datei anzeigen

@ -123,6 +123,9 @@ show-ping-requests = false
# can disable this setting to use the BungeeCord behavior. # can disable this setting to use the BungeeCord behavior.
failover-on-unexpected-server-disconnect = true failover-on-unexpected-server-disconnect = true
# Declares the proxy commands to 1.13+ clients.
announce-proxy-commands = true
# Enables the logging of commands # Enables the logging of commands
log-command-executions = false log-command-executions = false

Datei anzeigen

@ -0,0 +1,523 @@
package com.velocitypowered.proxy.command;
import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.fail;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.mojang.brigadier.arguments.IntegerArgumentType;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
import com.mojang.brigadier.tree.ArgumentCommandNode;
import com.mojang.brigadier.tree.CommandNode;
import com.mojang.brigadier.tree.LiteralCommandNode;
import com.velocitypowered.api.command.BrigadierCommand;
import com.velocitypowered.api.command.Command;
import com.velocitypowered.api.command.CommandMeta;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.command.RawCommand;
import com.velocitypowered.api.command.SimpleCommand;
import com.velocitypowered.proxy.plugin.MockEventManager;
import com.velocitypowered.proxy.plugin.VelocityEventManager;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import org.checkerframework.checker.nullness.qual.NonNull;
import org.junit.jupiter.api.Test;
public class CommandManagerTests {
private static final VelocityEventManager EVENT_MANAGER = new MockEventManager();
static {
Runtime.getRuntime().addShutdownHook(new Thread(() -> {
try {
EVENT_MANAGER.shutdown();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}));
}
static VelocityCommandManager createManager() {
return new VelocityCommandManager(EVENT_MANAGER);
}
@Test
void testConstruction() {
VelocityCommandManager manager = createManager();
assertFalse(manager.hasCommand("foo"));
assertTrue(manager.getDispatcher().getRoot().getChildren().isEmpty());
assertFalse(manager.execute(MockCommandSource.INSTANCE, "foo"));
assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "bar"));
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "").join().isEmpty());
}
@Test
void testBrigadierRegister() {
VelocityCommandManager manager = createManager();
LiteralCommandNode<CommandSource> node = LiteralArgumentBuilder
.<CommandSource>literal("foo")
.build();
BrigadierCommand command = new BrigadierCommand(node);
manager.register(command);
assertEquals(node, command.getNode());
assertTrue(manager.hasCommand("fOo"));
LiteralCommandNode<CommandSource> barNode = LiteralArgumentBuilder
.<CommandSource>literal("bar")
.build();
BrigadierCommand aliasesCommand = new BrigadierCommand(barNode);
CommandMeta meta = manager.metaBuilder(aliasesCommand)
.aliases("baZ")
.build();
assertEquals(ImmutableSet.of("bar", "baz"), meta.getAliases());
assertTrue(meta.getHints().isEmpty());
manager.register(meta, aliasesCommand);
assertTrue(manager.hasCommand("bAr"));
assertTrue(manager.hasCommand("Baz"));
}
@Test
void testSimpleRegister() {
VelocityCommandManager manager = createManager();
SimpleCommand command = new NoopSimpleCommand();
manager.register("Foo", command);
assertTrue(manager.hasCommand("foO"));
manager.unregister("fOo");
assertFalse(manager.hasCommand("foo"));
manager.register("foo", command, "bAr", "BAZ");
assertTrue(manager.hasCommand("bar"));
assertTrue(manager.hasCommand("bAz"));
}
@Test
void testRawRegister() {
VelocityCommandManager manager = createManager();
RawCommand command = new NoopRawCommand();
assertThrows(IllegalArgumentException.class, () -> manager.register(command),
"no aliases throws");
manager.register(command, "foO", "BAR");
assertTrue(manager.hasCommand("fOo"));
assertTrue(manager.hasCommand("bar"));
}
@Test
void testDeprecatedRegister() {
VelocityCommandManager manager = createManager();
Command command = new NoopDeprecatedCommand();
manager.register("foo", command);
assertTrue(manager.hasCommand("foO"));
}
@Test
void testAlreadyRegisteredThrows() {
VelocityCommandManager manager = createManager();
manager.register("bar", new NoopDeprecatedCommand());
assertThrows(IllegalArgumentException.class, () ->
manager.register("BAR", new NoopSimpleCommand()));
assertThrows(IllegalArgumentException.class, () -> {
CommandMeta meta = manager.metaBuilder("baz")
.aliases("BAr")
.build();
manager.register(meta, new NoopRawCommand());
});
}
@Test
void testBrigadierExecute() {
VelocityCommandManager manager = createManager();
AtomicBoolean executed = new AtomicBoolean(false);
AtomicBoolean checkedRequires = new AtomicBoolean(false);
LiteralCommandNode<CommandSource> node = LiteralArgumentBuilder
.<CommandSource>literal("buy")
.executes(context -> {
assertEquals(MockCommandSource.INSTANCE, context.getSource());
assertEquals("buy", context.getInput());
executed.set(true);
return 1;
})
.build();
CommandNode<CommandSource> quantityNode = RequiredArgumentBuilder
.<CommandSource, Integer>argument("quantity", IntegerArgumentType.integer(12, 16))
.requires(source -> {
assertEquals(MockCommandSource.INSTANCE, source);
checkedRequires.set(true);
return true;
})
.executes(context -> {
int argument = IntegerArgumentType.getInteger(context, "quantity");
assertEquals(14, argument);
executed.set(true);
return 1;
})
.build();
CommandNode<CommandSource> productNode = RequiredArgumentBuilder
.<CommandSource, String>argument("product", StringArgumentType.string())
.requires(source -> {
checkedRequires.set(true);
return false;
})
.executes(context -> fail("was executed"))
.build();
quantityNode.addChild(productNode);
node.addChild(quantityNode);
manager.register(new BrigadierCommand(node));
assertTrue(manager.executeAsync(MockCommandSource.INSTANCE, "buy ").join());
assertTrue(executed.compareAndSet(true, false), "was executed");
assertTrue(manager.executeImmediatelyAsync(MockCommandSource.INSTANCE, "buy 14").join());
assertTrue(checkedRequires.compareAndSet(true, false));
assertTrue(executed.get());
assertFalse(manager.execute(MockCommandSource.INSTANCE, "buy 9"),
"Invalid arg returns false");
assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "buy 12 bananas"));
assertTrue(checkedRequires.get());
}
@Test
void testSimpleExecute() {
VelocityCommandManager manager = createManager();
AtomicBoolean executed = new AtomicBoolean(false);
SimpleCommand command = invocation -> {
assertEquals(MockCommandSource.INSTANCE, invocation.source());
assertArrayEquals(new String[] {"bar", "254"}, invocation.arguments());
executed.set(true);
};
manager.register("foo", command);
assertTrue(manager.executeAsync(MockCommandSource.INSTANCE, "foo bar 254").join());
assertTrue(executed.get());
SimpleCommand noPermsCommand = new SimpleCommand() {
@Override
public void execute(final Invocation invocation) {
fail("was executed");
}
@Override
public boolean hasPermission(final Invocation invocation) {
return false;
}
};
manager.register("dangerous", noPermsCommand, "veryDangerous");
assertFalse(manager.execute(MockCommandSource.INSTANCE, "dangerous"));
assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "verydangerous 123"));
}
@Test
void testRawExecute() {
VelocityCommandManager manager = createManager();
AtomicBoolean executed = new AtomicBoolean(false);
RawCommand command = new RawCommand() {
@Override
public void execute(final Invocation invocation) {
assertEquals(MockCommandSource.INSTANCE, invocation.source());
assertEquals("lobby 23", invocation.arguments());
executed.set(true);
}
};
manager.register("sendMe", command);
assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "sendMe lobby 23"));
assertTrue(executed.compareAndSet(true, false));
RawCommand noArgsCommand = new RawCommand() {
@Override
public void execute(final Invocation invocation) {
assertEquals("", invocation.arguments());
executed.set(true);
}
};
manager.register("noargs", noArgsCommand);
assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "noargs"));
assertTrue(executed.get());
assertTrue(manager.executeImmediately(MockCommandSource.INSTANCE, "noargs "));
RawCommand noPermsCommand = new RawCommand() {
@Override
public void execute(final Invocation invocation) {
fail("was executed");
}
@Override
public boolean hasPermission(final Invocation invocation) {
return false;
}
};
manager.register("sendThem", noPermsCommand);
assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "sendThem foo"));
}
@Test
void testDeprecatedExecute() {
VelocityCommandManager manager = createManager();
AtomicBoolean executed = new AtomicBoolean(false);
Command command = new Command() {
@Override
public void execute(final CommandSource source, final String @NonNull [] args) {
assertEquals(MockCommandSource.INSTANCE, source);
assertArrayEquals(new String[] { "boo", "123" }, args);
executed.set(true);
}
};
manager.register("foo", command);
assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo boo 123"));
assertTrue(executed.get());
Command noPermsCommand = new Command() {
@Override
public boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
return false;
}
};
manager.register("oof", noPermsCommand, "veryOof");
assertFalse(manager.execute(MockCommandSource.INSTANCE, "veryOOF"));
assertFalse(manager.executeImmediately(MockCommandSource.INSTANCE, "ooF boo 54321"));
}
@Test
void testSuggestions() {
VelocityCommandManager manager = createManager();
LiteralCommandNode<CommandSource> brigadierNode = LiteralArgumentBuilder
.<CommandSource>literal("brigadier")
.build();
CommandNode<CommandSource> nameNode = RequiredArgumentBuilder
.<CommandSource, String>argument("name", StringArgumentType.string())
.build();
CommandNode<CommandSource> numberNode = RequiredArgumentBuilder
.<CommandSource, Integer>argument("quantity", IntegerArgumentType.integer())
.suggests((context, builder) -> builder.suggest(2).suggest(3).buildFuture())
.build();
nameNode.addChild(numberNode);
brigadierNode.addChild(nameNode);
manager.register(new BrigadierCommand(brigadierNode));
SimpleCommand simpleCommand = new SimpleCommand() {
@Override
public void execute(final Invocation invocation) {
fail();
}
@Override
public List<String> suggest(final Invocation invocation) {
switch (invocation.arguments().length) {
case 0:
return ImmutableList.of("foo", "bar");
case 1:
return ImmutableList.of("123");
default:
return ImmutableList.of();
}
}
};
manager.register("simple", simpleCommand);
RawCommand rawCommand = new RawCommand() {
@Override
public void execute(final Invocation invocation) {
fail();
}
@Override
public List<String> suggest(final Invocation invocation) {
switch (invocation.arguments()) {
case "":
return ImmutableList.of("foo", "baz");
case "foo ":
return ImmutableList.of("2", "3", "5", "7");
case "bar ":
return ImmutableList.of("11", "13", "17");
default:
return ImmutableList.of();
}
}
};
manager.register("raw", rawCommand);
Command deprecatedCommand = new Command() {
@Override
public List<String> suggest(
final CommandSource source, final String @NonNull [] currentArgs) {
switch (currentArgs.length) {
case 0:
return ImmutableList.of("boo", "scary");
case 1:
return ImmutableList.of("123", "456");
default:
return ImmutableList.of();
}
}
};
manager.register("deprecated", deprecatedCommand);
assertEquals(
ImmutableList.of("brigadier", "deprecated", "raw", "simple"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "").join(),
"literals are in alphabetical order");
assertEquals(
ImmutableList.of("brigadier"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "briga").join());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier")
.join().isEmpty());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier ")
.join().isEmpty());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier foo")
.join().isEmpty());
assertEquals(
ImmutableList.of("2", "3"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "brigadier foo ").join());
assertEquals(
ImmutableList.of("bar", "foo"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "simple ").join());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "simple")
.join().isEmpty());
assertEquals(
ImmutableList.of("123"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "simPle foo ").join());
assertEquals(
ImmutableList.of("baz", "foo"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "raw ").join());
assertEquals(
ImmutableList.of("2", "3", "5", "7"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "raw foo ").join());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "raw foo")
.join().isEmpty());
assertEquals(
ImmutableList.of("11", "13", "17"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "rAW bar ").join());
assertEquals(
ImmutableList.of("boo", "scary"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "deprecated ").join());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "deprecated")
.join().isEmpty());
assertEquals(
ImmutableList.of("123", "456"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "deprEcated foo ").join());
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "deprecated foo 789 ")
.join().isEmpty());
}
@Test
void testBrigadierSuggestionPermissions() {
VelocityCommandManager manager = createManager();
LiteralCommandNode<CommandSource> manageNode = LiteralArgumentBuilder
.<CommandSource>literal("manage")
.requires(source -> false)
.build();
CommandNode<CommandSource> idNode = RequiredArgumentBuilder
.<CommandSource, Integer>argument("id", IntegerArgumentType.integer(0))
.suggests((context, builder) -> fail("called suggestion builder"))
.build();
manageNode.addChild(idNode);
manager.register(new BrigadierCommand(manageNode));
// Brigadier doesn't call the children predicate when requesting suggestions.
// However, it won't query children if the source doesn't pass the parent
// #requires predicate.
assertTrue(manager.offerSuggestions(MockCommandSource.INSTANCE, "manage ")
.join().isEmpty());
}
@Test
void testHinting() {
VelocityCommandManager manager = createManager();
AtomicBoolean executed = new AtomicBoolean(false);
AtomicBoolean calledSuggestionProvider = new AtomicBoolean(false);
AtomicReference<String> expectedArgs = new AtomicReference<>();
RawCommand command = new RawCommand() {
@Override
public void execute(final Invocation invocation) {
assertEquals(expectedArgs.get(), invocation.arguments());
executed.set(true);
}
@Override
public List<String> suggest(final Invocation invocation) {
return ImmutableList.of("raw");
}
};
CommandNode<CommandSource> barHint = LiteralArgumentBuilder
.<CommandSource>literal("bar")
.executes(context -> fail("hints don't get executed"))
.build();
ArgumentCommandNode<CommandSource, Integer> numberArg = RequiredArgumentBuilder
.<CommandSource, Integer>argument("number", IntegerArgumentType.integer())
.suggests((context, builder) -> {
calledSuggestionProvider.set(true);
return builder.suggest("456").buildFuture();
})
.build();
barHint.addChild(numberArg);
CommandNode<CommandSource> bazHint = LiteralArgumentBuilder
.<CommandSource>literal("baz")
.build();
CommandMeta meta = manager.metaBuilder("foo")
.aliases("foo2")
.hint(barHint)
.hint(bazHint)
.build();
manager.register(meta, command);
expectedArgs.set("notBarOrBaz");
assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo notBarOrBaz"));
assertTrue(executed.compareAndSet(true, false));
expectedArgs.set("anotherArg 123");
assertTrue(manager.execute(MockCommandSource.INSTANCE, "Foo2 anotherArg 123"));
assertTrue(executed.compareAndSet(true, false));
expectedArgs.set("bar");
assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo bar"));
assertTrue(executed.compareAndSet(true, false));
expectedArgs.set("bar 123");
assertTrue(manager.execute(MockCommandSource.INSTANCE, "foo2 bar 123"));
assertTrue(executed.compareAndSet(true, false));
assertEquals(ImmutableList.of("bar", "baz", "raw"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "foo ").join());
assertFalse(calledSuggestionProvider.get());
assertEquals(ImmutableList.of("456"),
manager.offerSuggestions(MockCommandSource.INSTANCE, "foo bar ").join());
assertTrue(calledSuggestionProvider.compareAndSet(true, false));
assertEquals(ImmutableList.of(),
manager.offerSuggestions(MockCommandSource.INSTANCE, "foo2 baz ").join());
}
static class NoopSimpleCommand implements SimpleCommand {
@Override
public void execute(final Invocation invocation) {
}
}
static class NoopRawCommand implements RawCommand {
@Override
public void execute(final Invocation invocation) {
}
}
static class NoopDeprecatedCommand implements Command {
@Override
public void execute(final CommandSource source, final String @NonNull [] args) {
}
}
}

Datei anzeigen

@ -0,0 +1,20 @@
package com.velocitypowered.proxy.command;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.permission.Tristate;
import net.kyori.text.Component;
public class MockCommandSource implements CommandSource {
public static CommandSource INSTANCE = new MockCommandSource();
@Override
public void sendMessage(final Component component) {
}
@Override
public Tristate getPermissionValue(final String permission) {
return Tristate.UNDEFINED;
}
}

Datei anzeigen

@ -0,0 +1,11 @@
package com.velocitypowered.proxy.plugin;
/**
* A mock {@link VelocityEventManager}. Must be shutdown after use!
*/
public class MockEventManager extends VelocityEventManager {
public MockEventManager() {
super(MockPluginManager.INSTANCE);
}
}

Datei anzeigen

@ -0,0 +1,38 @@
package com.velocitypowered.proxy.plugin;
import com.google.common.collect.ImmutableList;
import com.velocitypowered.api.plugin.PluginContainer;
import com.velocitypowered.api.plugin.PluginManager;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Optional;
public class MockPluginManager implements PluginManager {
public static PluginManager INSTANCE = new MockPluginManager();
@Override
public Optional<PluginContainer> fromInstance(final Object instance) {
return Optional.empty();
}
@Override
public Optional<PluginContainer> getPlugin(final String id) {
return Optional.empty();
}
@Override
public Collection<PluginContainer> getPlugins() {
return ImmutableList.of();
}
@Override
public boolean isLoaded(final String id) {
return false;
}
@Override
public void addToClasspath(final Object plugin, final Path path) {
}
}