Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
New command API (#330)
Dieser Commit ist enthalten in:
Ursprung
5a515f37a3
Commit
6cc6e0f641
@ -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;
|
||||
}
|
||||
}
|
@ -1,59 +1,90 @@
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* Represents a command that can be executed by a {@link CommandSource}, such as a {@link
|
||||
* com.velocitypowered.api.proxy.Player} or the console.
|
||||
* Represents a command that can be executed by a {@link CommandSource}
|
||||
* 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 {
|
||||
|
||||
/**
|
||||
* Executes the command for the specified {@link CommandSource}.
|
||||
* Executes the command for the specified source.
|
||||
*
|
||||
* @param source the source of this command
|
||||
* @param args the arguments for this command
|
||||
* @param source the source to execute the command for
|
||||
* @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 currentArgs the current, partial arguments for this command
|
||||
* @return tab complete suggestions
|
||||
* @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}
|
||||
*/
|
||||
default List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
@Deprecated
|
||||
default List<String> suggest(final CommandSource source, final String @NonNull [] currentArgs) {
|
||||
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 currentArgs the current, partial arguments for this command
|
||||
* @return tab complete suggestions
|
||||
* @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}
|
||||
*/
|
||||
default CompletableFuture<List<String>> suggestAsync(CommandSource source,
|
||||
String @NonNull [] currentArgs) {
|
||||
@Deprecated
|
||||
default CompletableFuture<List<String>> suggestAsync(final CommandSource source,
|
||||
String @NonNull [] currentArgs) {
|
||||
return CompletableFuture.completedFuture(suggest(source, currentArgs));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to check if the {@code source} has permission to use this command with the provided
|
||||
* {@code args}.
|
||||
* Tests to check if the source has permission to perform the command with
|
||||
* the provided arguments.
|
||||
*
|
||||
* <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 args the arguments for this command
|
||||
* @return whether the source has permission
|
||||
* @param source the source to execute the command for
|
||||
* @param args the arguments for the command
|
||||
* @return {@code true} if the source has permission
|
||||
* @deprecated see {@link Command}
|
||||
*/
|
||||
default boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
@Deprecated
|
||||
default boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
@ -1,87 +1,125 @@
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import com.velocitypowered.api.event.command.CommandExecuteEvent;
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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 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.
|
||||
* Prefer {@link #register(String, Command, String...)} instead.
|
||||
* Prefer {@link #register(String, Command, String...)}
|
||||
*/
|
||||
@Deprecated
|
||||
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 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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
*/
|
||||
void unregister(String alias);
|
||||
|
||||
/**
|
||||
* Calls CommandExecuteEvent and attempts to execute a command using the specified {@code cmdLine}
|
||||
* in a blocking fashion.
|
||||
* Attempts to execute a command from the given {@code cmdLine} 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
|
||||
* @return true if the command was found and executed, false if it was not
|
||||
*
|
||||
* @deprecated This method will block current thread during event call and command execution.
|
||||
* Prefer {@link #executeAsync(CommandSource, String)} instead.
|
||||
* @return {@code true} if the command was found and executed
|
||||
* @deprecated this method blocks the current thread during the event call and
|
||||
* the command execution. Prefer {@link #executeAsync(CommandSource, String)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean execute(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Attempts to execute a command using the specified {@code cmdLine} in a blocking fashion without
|
||||
* calling CommandExecuteEvent.
|
||||
* Attempts to execute a command from the given {@code cmdLine} without
|
||||
* 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
|
||||
* @return true if the command was found and executed, false if it was not
|
||||
*
|
||||
* @deprecated This method will block current thread during event and command execution.
|
||||
* @return {@code true} if the command was found and executed
|
||||
* @deprecated this methods blocks the current thread during the command execution.
|
||||
* Prefer {@link #executeImmediatelyAsync(CommandSource, String)} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
boolean executeImmediately(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Calls CommandExecuteEvent and attempts to execute a command from the specified {@code cmdLine}
|
||||
* async.
|
||||
* Attempts to asynchronously execute a command from the given {@code cmdLine}.
|
||||
*
|
||||
* @param source the command's source
|
||||
* @param source the source to execute the command for
|
||||
* @param cmdLine the command to run
|
||||
* @return A future that will be completed with the result of the command execution.
|
||||
* Can be completed exceptionally if exception was thrown during execution.
|
||||
* @return a future that may be completed with the result of the command execution.
|
||||
* Can be completed exceptionally if an exception is thrown during execution.
|
||||
*/
|
||||
CompletableFuture<Boolean> executeAsync(CommandSource source, String cmdLine);
|
||||
|
||||
/**
|
||||
* Attempts to execute a command from the specified {@code cmdLine} async
|
||||
* without calling CommandExecuteEvent.
|
||||
* Attempts to asynchronously execute a command from the given {@code cmdLine}
|
||||
* 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
|
||||
* @return A future that will be completed with the result of the command execution.
|
||||
* Can be completed exceptionally if exception was thrown during execution.
|
||||
* @return a future that may be completed with the result of the command execution.
|
||||
* Can be completed exceptionally if an exception is thrown during execution.
|
||||
*/
|
||||
CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine);
|
||||
}
|
||||
|
57
api/src/main/java/com/velocitypowered/api/command/CommandMeta.java
Normale Datei
57
api/src/main/java/com/velocitypowered/api/command/CommandMeta.java
Normale Datei
@ -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();
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -1,67 +1,97 @@
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
/**
|
||||
* A specialized sub-interface of {@code Command} which indicates that the proxy should pass a
|
||||
* raw command to the command. This is useful for bolting on external command frameworks to
|
||||
* Velocity.
|
||||
* A specialized sub-interface of {@code Command} which indicates the proxy should pass
|
||||
* the command and its arguments directly without further processing.
|
||||
* This is useful for bolting on external command frameworks to Velocity.
|
||||
*/
|
||||
public interface RawCommand extends Command {
|
||||
/**
|
||||
* 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);
|
||||
public interface RawCommand extends InvocableCommand<RawCommand.Invocation> {
|
||||
|
||||
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));
|
||||
}
|
||||
|
||||
/**
|
||||
* Provides tab complete suggestions for a command for a specified {@link CommandSource}.
|
||||
*
|
||||
* @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());
|
||||
@Override
|
||||
default void execute(Invocation invocation) {
|
||||
// Guarantees ABI compatibility
|
||||
}
|
||||
|
||||
/**
|
||||
* 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
|
||||
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();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
@Override
|
||||
default CompletableFuture<List<String>> suggestAsync(CommandSource source,
|
||||
String @NonNull [] currentArgs) {
|
||||
default CompletableFuture<List<String>> suggestAsync(final CommandSource source,
|
||||
final String @NonNull [] 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
|
||||
default boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
default boolean hasPermission(final CommandSource source, final String @NonNull [] args) {
|
||||
return hasPermission(source, String.join(" ", args));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests to check if the {@code source} has permission to use this command with the provided
|
||||
* {@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
|
||||
* Contains the invocation data for a raw command.
|
||||
*/
|
||||
default boolean hasPermission(CommandSource source, String commandLine) {
|
||||
return true;
|
||||
interface Invocation extends CommandInvocation<String> {
|
||||
|
||||
/**
|
||||
* Returns the used alias to execute the command.
|
||||
*
|
||||
* @return the used command alias
|
||||
*/
|
||||
String alias();
|
||||
}
|
||||
}
|
||||
|
20
api/src/main/java/com/velocitypowered/api/command/SimpleCommand.java
Normale Datei
20
api/src/main/java/com/velocitypowered/api/command/SimpleCommand.java
Normale Datei
@ -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 []> {
|
||||
|
||||
}
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
/**
|
||||
* Provides a simple command framework.
|
||||
* Provides a command framework.
|
||||
*/
|
||||
package com.velocitypowered.api.command;
|
@ -47,12 +47,12 @@ allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
|
||||
|
||||
// for kyoripowered dependencies
|
||||
maven {
|
||||
url 'https://oss.sonatype.org/content/groups/public/'
|
||||
}
|
||||
|
||||
|
||||
// Brigadier
|
||||
maven {
|
||||
url "https://libraries.minecraft.net"
|
||||
|
@ -21,11 +21,11 @@ import com.velocitypowered.api.util.ProxyVersion;
|
||||
import com.velocitypowered.api.util.bossbar.BossBar;
|
||||
import com.velocitypowered.api.util.bossbar.BossBarColor;
|
||||
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.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.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.console.VelocityConsole;
|
||||
@ -194,7 +194,7 @@ public class VelocityServer implements ProxyServer, ForwardingAudience {
|
||||
commandManager.register("velocity", new VelocityCommand(this));
|
||||
commandManager.register("server", new ServerCommand(this));
|
||||
commandManager.register("shutdown", new ShutdownCommand(this),"end");
|
||||
commandManager.register("glist", new GlistCommand(this));
|
||||
new GlistCommand(this).register();
|
||||
|
||||
try {
|
||||
Path configPath = Paths.get("velocity.toml");
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -1,273 +1,229 @@
|
||||
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.google.common.collect.Lists;
|
||||
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.CommandManager;
|
||||
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.api.event.command.CommandExecuteEvent;
|
||||
import com.velocitypowered.api.event.command.CommandExecuteEvent.CommandResult;
|
||||
import com.velocitypowered.proxy.plugin.VelocityEventManager;
|
||||
import com.velocitypowered.proxy.util.BrigadierUtils;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import net.kyori.adventure.text.TextComponent;
|
||||
import net.kyori.adventure.text.format.NamedTextColor;
|
||||
|
||||
public class VelocityCommandManager implements CommandManager {
|
||||
|
||||
private final Map<String, RawCommand> commands = new HashMap<>();
|
||||
private final CommandDispatcher<CommandSource> dispatcher;
|
||||
private final VelocityEventManager eventManager;
|
||||
|
||||
public VelocityCommandManager(VelocityEventManager eventManager) {
|
||||
this.eventManager = eventManager;
|
||||
public VelocityCommandManager(final VelocityEventManager 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
|
||||
@Deprecated
|
||||
public void register(final Command command, final String... aliases) {
|
||||
Preconditions.checkArgument(aliases.length > 0, "no aliases provided");
|
||||
register(aliases[0], command, Arrays.copyOfRange(aliases, 1, aliases.length));
|
||||
}
|
||||
|
||||
@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(command, "command");
|
||||
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);
|
||||
this.commands.put(alias.toLowerCase(Locale.ENGLISH), rawCmd);
|
||||
@Override
|
||||
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++) {
|
||||
final String alias1 = otherAliases[i];
|
||||
Preconditions.checkNotNull(alias1, "alias at index %s", i + 1);
|
||||
this.commands.put(alias1.toLowerCase(Locale.ENGLISH), rawCmd);
|
||||
@Override
|
||||
public void register(final CommandMeta meta, final Command command) {
|
||||
Preconditions.checkNotNull(meta, "meta");
|
||||
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
|
||||
public void unregister(final String alias) {
|
||||
Preconditions.checkNotNull(alias, "name");
|
||||
this.commands.remove(alias.toLowerCase(Locale.ENGLISH));
|
||||
Preconditions.checkNotNull(alias, "alias");
|
||||
CommandNode<CommandSource> node =
|
||||
dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (node != null) {
|
||||
dispatcher.getRoot().getChildren().remove(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Calls CommandExecuteEvent.
|
||||
* @param source the command's source
|
||||
* @param cmd the command
|
||||
* @return CompletableFuture of event
|
||||
* Fires a {@link CommandExecuteEvent}.
|
||||
*
|
||||
* @param source the source to execute the command for
|
||||
* @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(cmd, "cmd");
|
||||
return eventManager.fire(new CommandExecuteEvent(source, cmd));
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
return eventManager.fire(new CommandExecuteEvent(source, cmdLine));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean execute(CommandSource source, String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "source");
|
||||
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);
|
||||
public boolean execute(final CommandSource source, final String cmdLine) {
|
||||
return executeAsync(source, cmdLine).join();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean executeImmediately(CommandSource source, String cmdLine) {
|
||||
public boolean executeImmediately(final CommandSource source, final 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);
|
||||
}
|
||||
RawCommand command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (command == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ParseResults<CommandSource> results = parse(cmdLine, source, true);
|
||||
try {
|
||||
if (!command.hasPermission(source, args)) {
|
||||
return false;
|
||||
return dispatcher.execute(results) != BrigadierCommand.FORWARD;
|
||||
} 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 true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
} catch (final Exception e) {
|
||||
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + source, e);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> executeAsync(CommandSource source, String cmdLine) {
|
||||
CompletableFuture<Boolean> result = new CompletableFuture<>();
|
||||
callCommandEvent(source, cmdLine).thenAccept(event -> {
|
||||
public CompletableFuture<Boolean> executeAsync(final CommandSource source, final String cmdLine) {
|
||||
Preconditions.checkNotNull(source, "source");
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
|
||||
return callCommandEvent(source, cmdLine).thenApply(event -> {
|
||||
CommandResult commandResult = event.getResult();
|
||||
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 false;
|
||||
}
|
||||
return executeImmediately(source, commandResult.getCommand().orElse(event.getCommand()));
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Boolean> executeImmediatelyAsync(CommandSource source, String cmdLine) {
|
||||
public CompletableFuture<Boolean> executeImmediatelyAsync(
|
||||
final CommandSource source, final 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());
|
||||
return CompletableFuture.supplyAsync(
|
||||
() -> executeImmediately(source, cmdLine), eventManager.getService());
|
||||
}
|
||||
|
||||
/**
|
||||
* Offer suggestions to fill in the command.
|
||||
* @param source the source for the command
|
||||
* Returns suggestions to fill in the given command.
|
||||
*
|
||||
* @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
|
||||
* @return a {@link CompletableFuture} eventually completed with a {@link List},
|
||||
* possibly empty
|
||||
*/
|
||||
public CompletableFuture<List<String>> offerSuggestions(CommandSource source, String cmdLine) {
|
||||
public CompletableFuture<List<String>> offerSuggestions(final CommandSource source,
|
||||
final 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());
|
||||
}
|
||||
ParseResults<CommandSource> parse = parse(cmdLine, source, false);
|
||||
return dispatcher.getCompletionSuggestions(parse)
|
||||
.thenApply(suggestions -> Lists.transform(suggestions.getList(), Suggestion::getText));
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
private ParseResults<CommandSource> parse(final String cmdLine, final CommandSource source,
|
||||
final boolean trim) {
|
||||
String normalized = BrigadierUtils.normalizeInput(cmdLine, trim);
|
||||
return dispatcher.parse(normalized, source);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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}
|
||||
* Returns whether the given alias is registered on this manager.
|
||||
*
|
||||
* @param alias the command alias to check
|
||||
* @return {@code true} if the alias is registered
|
||||
*/
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
return command.hasPermission(source, args);
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(
|
||||
"Unable to invoke suggestions for command " + alias + " for " + source, e);
|
||||
}
|
||||
public boolean hasCommand(final String alias) {
|
||||
Preconditions.checkNotNull(alias, "alias");
|
||||
return dispatcher.getRoot().getChild(alias.toLowerCase(Locale.ENGLISH)) != null;
|
||||
}
|
||||
|
||||
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
|
||||
public void execute(CommandSource source, String commandLine) {
|
||||
delegate.execute(source, split(commandLine));
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<List<String>> suggest(CommandSource source, String currentLine) {
|
||||
return delegate.suggestAsync(source, split(currentLine));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String commandLine) {
|
||||
return delegate.hasPermission(source, split(commandLine));
|
||||
}
|
||||
|
||||
static RawCommand wrap(Command command) {
|
||||
if (command instanceof RawCommand) {
|
||||
return (RawCommand) command;
|
||||
}
|
||||
return new RegularCommandWrapper(command);
|
||||
}
|
||||
public CommandDispatcher<CommandSource> getDispatcher() {
|
||||
return dispatcher;
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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.server.RegisteredServer;
|
@ -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());
|
||||
}
|
||||
}
|
@ -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 com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
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.event.ClickEvent;
|
||||
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;
|
||||
private final ProxyServer server;
|
||||
@ -30,7 +29,10 @@ public class ServerCommand implements Command {
|
||||
}
|
||||
|
||||
@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)) {
|
||||
source.sendMessage(TextComponent.of("Only players may run this command.",
|
||||
NamedTextColor.RED));
|
||||
@ -102,9 +104,11 @@ public class ServerCommand implements Command {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
|
||||
Stream<String> possibilities = Stream.concat(Stream.of("all"), server.getAllServers()
|
||||
.stream().map(rs -> rs.getServerInfo().getName()));
|
||||
public List<String> suggest(final SimpleCommand.Invocation invocation) {
|
||||
final String[] currentArgs = invocation.arguments();
|
||||
Stream<String> possibilities = server.getAllServers().stream()
|
||||
.map(rs -> rs.getServerInfo().getName());
|
||||
|
||||
if (currentArgs.length == 0) {
|
||||
return possibilities.collect(Collectors.toList());
|
||||
} else if (currentArgs.length == 1) {
|
||||
@ -117,7 +121,7 @@ public class ServerCommand implements Command {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(CommandSource source, String @NonNull [] args) {
|
||||
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
|
||||
public boolean hasPermission(final SimpleCommand.Invocation invocation) {
|
||||
return invocation.source().getPermissionValue("velocity.command.server") != Tristate.FALSE;
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
}
|
@ -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.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableMap;
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.SimpleCommand;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
@ -25,16 +25,28 @@ import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
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.
|
||||
*
|
||||
* @param server the Velocity server
|
||||
*/
|
||||
public VelocityCommand(VelocityServer server) {
|
||||
this.subcommands = ImmutableMap.<String, Command>builder()
|
||||
this.commands = ImmutableMap.<String, SubCommand>builder()
|
||||
.put("version", new Info(server))
|
||||
.put("plugins", new Plugins(server))
|
||||
.put("reload", new Reload(server))
|
||||
@ -42,7 +54,7 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
private void usage(CommandSource source) {
|
||||
String availableCommands = subcommands.entrySet().stream()
|
||||
String availableCommands = commands.entrySet().stream()
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(Collectors.joining("|"));
|
||||
@ -51,13 +63,16 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
@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) {
|
||||
usage(source);
|
||||
return;
|
||||
}
|
||||
|
||||
Command command = subcommands.get(args[0].toLowerCase(Locale.US));
|
||||
SubCommand command = commands.get(args[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
usage(source);
|
||||
return;
|
||||
@ -68,16 +83,19 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
@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) {
|
||||
return subcommands.entrySet().stream()
|
||||
return commands.entrySet().stream()
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
.map(Map.Entry::getKey)
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
if (currentArgs.length == 1) {
|
||||
return subcommands.entrySet().stream()
|
||||
return commands.entrySet().stream()
|
||||
.filter(e -> e.getKey().regionMatches(true, 0, currentArgs[0], 0,
|
||||
currentArgs[0].length()))
|
||||
.filter(e -> e.getValue().hasPermission(source, new String[0]))
|
||||
@ -85,7 +103,7 @@ public class VelocityCommand implements Command {
|
||||
.collect(ImmutableList.toImmutableList());
|
||||
}
|
||||
|
||||
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||
SubCommand command = commands.get(currentArgs[0].toLowerCase(Locale.US));
|
||||
if (command == null) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
@ -95,11 +113,14 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
@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) {
|
||||
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) {
|
||||
return true;
|
||||
}
|
||||
@ -108,7 +129,7 @@ public class VelocityCommand implements Command {
|
||||
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 final VelocityServer server;
|
||||
@ -136,12 +157,12 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Info implements Command {
|
||||
private static class Info implements SubCommand {
|
||||
|
||||
private final ProxyServer server;
|
||||
|
||||
@ -189,12 +210,12 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
||||
|
||||
private static class Plugins implements Command {
|
||||
private static class Plugins implements SubCommand {
|
||||
|
||||
private final ProxyServer server;
|
||||
|
||||
@ -260,7 +281,7 @@ public class VelocityCommand implements Command {
|
||||
}
|
||||
|
||||
@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;
|
||||
}
|
||||
}
|
@ -346,6 +346,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return advanced.isFailoverOnUnexpectedServerDisconnect();
|
||||
}
|
||||
|
||||
public boolean isAnnounceProxyCommands() {
|
||||
return advanced.isAnnounceProxyCommands();
|
||||
}
|
||||
|
||||
public boolean isLogCommandExecutions() {
|
||||
return advanced.isLogCommandExecutions();
|
||||
}
|
||||
@ -586,6 +590,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
private boolean bungeePluginMessageChannel = true;
|
||||
private boolean showPingRequests = false;
|
||||
private boolean failoverOnUnexpectedServerDisconnect = true;
|
||||
private boolean announceProxyCommands = true;
|
||||
private boolean logCommandExecutions = false;
|
||||
|
||||
private Advanced() {
|
||||
@ -604,6 +609,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
this.showPingRequests = config.getOrElse("show-ping-requests", false);
|
||||
this.failoverOnUnexpectedServerDisconnect = config
|
||||
.getOrElse("failover-on-unexpected-server-disconnect", true);
|
||||
this.announceProxyCommands = config.getOrElse("announce-proxy-commands", true);
|
||||
this.logCommandExecutions = config.getOrElse("log-command-executions", false);
|
||||
}
|
||||
}
|
||||
@ -648,6 +654,10 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
return failoverOnUnexpectedServerDisconnect;
|
||||
}
|
||||
|
||||
public boolean isAnnounceProxyCommands() {
|
||||
return announceProxyCommands;
|
||||
}
|
||||
|
||||
public boolean isLogCommandExecutions() {
|
||||
return logCommandExecutions;
|
||||
}
|
||||
@ -665,6 +675,7 @@ public class VelocityConfiguration implements ProxyConfig {
|
||||
+ ", bungeePluginMessageChannel=" + bungeePluginMessageChannel
|
||||
+ ", showPingRequests=" + showPingRequests
|
||||
+ ", failoverOnUnexpectedServerDisconnect=" + failoverOnUnexpectedServerDisconnect
|
||||
+ ", announceProxyCommands=" + announceProxyCommands
|
||||
+ ", logCommandExecutions=" + logCommandExecutions
|
||||
+ '}';
|
||||
}
|
||||
|
@ -3,10 +3,9 @@ package com.velocitypowered.proxy.connection.backend;
|
||||
import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder.getBungeeCordChannel;
|
||||
|
||||
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.tree.LiteralCommandNode;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.command.PlayerAvailableCommandsEvent;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
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.protocol.MinecraftPacket;
|
||||
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.Disconnect;
|
||||
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.Unpooled;
|
||||
import io.netty.handler.timeout.ReadTimeoutException;
|
||||
import java.util.Collection;
|
||||
|
||||
public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@ -164,23 +163,18 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public boolean handle(AvailableCommands commands) {
|
||||
// Inject commands from the proxy.
|
||||
for (String command : server.getCommandManager().getAllRegisteredCommands()) {
|
||||
if (!server.getCommandManager().hasPermission(serverConn.getPlayer(), command)) {
|
||||
continue;
|
||||
RootCommandNode<CommandSource> rootNode = commands.getRootNode();
|
||||
if (server.getConfiguration().isAnnounceProxyCommands()) {
|
||||
// Inject commands from the proxy.
|
||||
Collection<CommandNode<CommandSource>> proxyNodes = server.getCommandManager().getDispatcher()
|
||||
.getRoot().getChildren();
|
||||
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(
|
||||
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), commands.getRootNode()))
|
||||
new PlayerAvailableCommandsEvent(serverConn.getPlayer(), rootNode))
|
||||
.thenAcceptAsync(event -> playerConnection.write(commands), playerConnection.eventLoop());
|
||||
return true;
|
||||
}
|
||||
|
@ -130,7 +130,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
server.getCommandManager().callCommandEvent(player, msg.substring(1))
|
||||
.thenComposeAsync(event -> processCommandExecuteResult(originalCommand,
|
||||
event.getResult()))
|
||||
.whenCompleteAsync((ignored, throwable) -> {
|
||||
.whenComplete((ignored, throwable) -> {
|
||||
if (server.getConfiguration().isLogCommandExecutions()) {
|
||||
logger.info("{} -> executed command /{}", player, originalCommand);
|
||||
}
|
||||
@ -414,7 +414,6 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
for (String suggestion : suggestions) {
|
||||
offers.add(new Offer(suggestion));
|
||||
}
|
||||
|
||||
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
|
||||
if (startPos > 0) {
|
||||
TabCompleteResponse resp = new TabCompleteResponse();
|
||||
@ -460,9 +459,10 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
String command = request.getCommand().substring(1);
|
||||
server.getCommandManager().offerSuggestions(player, command)
|
||||
.thenAcceptAsync(offers -> {
|
||||
boolean needsSlash = player.getProtocolVersion().compareTo(MINECRAFT_1_13) < 0;
|
||||
try {
|
||||
for (String offer : offers) {
|
||||
response.getOffers().add(new Offer(offer, null));
|
||||
response.getOffers().add(new Offer(needsSlash ? "/" + offer : offer, null));
|
||||
}
|
||||
response.getOffers().sort(null);
|
||||
player.getConnection().write(response);
|
||||
|
@ -71,16 +71,11 @@ public final class VelocityConsole extends SimpleTerminalConsole implements Cons
|
||||
.appName("Velocity")
|
||||
.completer((reader, parsedLine, list) -> {
|
||||
try {
|
||||
boolean isCommand = parsedLine.line().indexOf(' ') == -1;
|
||||
List<String> offers = this.server.getCommandManager()
|
||||
.offerSuggestions(this, parsedLine.line())
|
||||
.join(); // Console doesn't get harmed much by this...
|
||||
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) {
|
||||
logger.error("An error occurred while trying to perform tab completion.", e);
|
||||
|
@ -16,6 +16,7 @@ import com.mojang.brigadier.tree.ArgumentCommandNode;
|
||||
import com.mojang.brigadier.tree.CommandNode;
|
||||
import com.mojang.brigadier.tree.LiteralCommandNode;
|
||||
import com.mojang.brigadier.tree.RootCommandNode;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.network.ProtocolVersion;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
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_HAS_SUGGESTIONS = 0x10;
|
||||
|
||||
// Note: Velocity doesn't use Brigadier for command handling. This may change in Velocity 2.0.0.
|
||||
private @MonotonicNonNull RootCommandNode<Object> rootNode;
|
||||
private @MonotonicNonNull RootCommandNode<CommandSource> rootNode;
|
||||
|
||||
/**
|
||||
* Returns the root node.
|
||||
* @return the root node
|
||||
*/
|
||||
public RootCommandNode<Object> getRootNode() {
|
||||
public RootCommandNode<CommandSource> getRootNode() {
|
||||
if (rootNode == null) {
|
||||
throw new IllegalStateException("Packet not yet deserialized");
|
||||
}
|
||||
@ -87,16 +87,16 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
}
|
||||
|
||||
int rootIdx = ProtocolUtils.readVarInt(buf);
|
||||
rootNode = (RootCommandNode<Object>) wireNodes[rootIdx].built;
|
||||
rootNode = (RootCommandNode<CommandSource>) wireNodes[rootIdx].built;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, Direction direction, ProtocolVersion protocolVersion) {
|
||||
// Assign all the children an index.
|
||||
Deque<CommandNode<Object>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode));
|
||||
Object2IntMap<CommandNode<Object>> idMappings = new Object2IntLinkedOpenHashMap<>();
|
||||
Deque<CommandNode<CommandSource>> childrenQueue = new ArrayDeque<>(ImmutableList.of(rootNode));
|
||||
Object2IntMap<CommandNode<CommandSource>> idMappings = new Object2IntLinkedOpenHashMap<>();
|
||||
while (!childrenQueue.isEmpty()) {
|
||||
CommandNode<Object> child = childrenQueue.poll();
|
||||
CommandNode<CommandSource> child = childrenQueue.poll();
|
||||
if (!idMappings.containsKey(child)) {
|
||||
idMappings.put(child, idMappings.size());
|
||||
childrenQueue.addAll(child.getChildren());
|
||||
@ -105,14 +105,14 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
|
||||
// Now serialize the children.
|
||||
ProtocolUtils.writeVarInt(buf, idMappings.size());
|
||||
for (CommandNode<Object> child : idMappings.keySet()) {
|
||||
for (CommandNode<CommandSource> child : idMappings.keySet()) {
|
||||
serializeNode(child, buf, idMappings);
|
||||
}
|
||||
ProtocolUtils.writeVarInt(buf, idMappings.getInt(rootNode));
|
||||
}
|
||||
|
||||
private static void serializeNode(CommandNode<Object> node, ByteBuf buf,
|
||||
Object2IntMap<CommandNode<Object>> idMappings) {
|
||||
private static void serializeNode(CommandNode<CommandSource> node, ByteBuf buf,
|
||||
Object2IntMap<CommandNode<CommandSource>> idMappings) {
|
||||
byte flags = 0;
|
||||
if (node.getRedirect() != null) {
|
||||
flags |= FLAG_IS_REDIRECT;
|
||||
@ -127,7 +127,7 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
flags |= NODE_TYPE_LITERAL;
|
||||
} else if (node instanceof ArgumentCommandNode<?, ?>) {
|
||||
flags |= NODE_TYPE_ARGUMENT;
|
||||
if (((ArgumentCommandNode) node).getCustomSuggestions() != null) {
|
||||
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
|
||||
flags |= FLAG_HAS_SUGGESTIONS;
|
||||
}
|
||||
} else {
|
||||
@ -136,7 +136,7 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
|
||||
buf.writeByte(flags);
|
||||
ProtocolUtils.writeVarInt(buf, node.getChildren().size());
|
||||
for (CommandNode<Object> child : node.getChildren()) {
|
||||
for (CommandNode<CommandSource> child : node.getChildren()) {
|
||||
ProtocolUtils.writeVarInt(buf, idMappings.getInt(child));
|
||||
}
|
||||
if (node.getRedirect() != null) {
|
||||
@ -145,20 +145,17 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
|
||||
if (node instanceof ArgumentCommandNode<?, ?>) {
|
||||
ProtocolUtils.writeString(buf, node.getName());
|
||||
ArgumentPropertyRegistry.serialize(buf, ((ArgumentCommandNode) node).getType());
|
||||
ArgumentPropertyRegistry.serialize(buf,
|
||||
((ArgumentCommandNode<CommandSource, ?>) node).getType());
|
||||
|
||||
if (((ArgumentCommandNode) node).getCustomSuggestions() != null) {
|
||||
// The unchecked cast is required, but it is not particularly relevant because we check for
|
||||
// a more specific type later. (Even then, we only pull out one field.)
|
||||
@SuppressWarnings("unchecked")
|
||||
SuggestionProvider<Object> provider = ((ArgumentCommandNode) node).getCustomSuggestions();
|
||||
|
||||
if (!(provider instanceof ProtocolSuggestionProvider)) {
|
||||
throw new IllegalArgumentException("Suggestion provider " + provider.getClass().getName()
|
||||
+ " is not valid.");
|
||||
if (((ArgumentCommandNode<CommandSource, ?>) node).getCustomSuggestions() != null) {
|
||||
SuggestionProvider<CommandSource> provider = ((ArgumentCommandNode<CommandSource, ?>) node)
|
||||
.getCustomSuggestions();
|
||||
String name = "minecraft:ask_server";
|
||||
if (provider instanceof ProtocolSuggestionProvider) {
|
||||
name = ((ProtocolSuggestionProvider) provider).name;
|
||||
}
|
||||
|
||||
ProtocolUtils.writeString(buf, ((ProtocolSuggestionProvider) provider).name);
|
||||
ProtocolUtils.writeString(buf, name);
|
||||
}
|
||||
} else if (node instanceof LiteralCommandNode<?>) {
|
||||
ProtocolUtils.writeString(buf, node.getName());
|
||||
@ -188,7 +185,7 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
String name = ProtocolUtils.readString(buf);
|
||||
ArgumentType<?> argumentType = ArgumentPropertyRegistry.deserialize(buf);
|
||||
|
||||
RequiredArgumentBuilder<Object, ?> argumentBuilder = RequiredArgumentBuilder
|
||||
RequiredArgumentBuilder<CommandSource, ?> argumentBuilder = RequiredArgumentBuilder
|
||||
.argument(name, argumentType);
|
||||
if ((flags & FLAG_HAS_SUGGESTIONS) != 0) {
|
||||
argumentBuilder.suggests(new ProtocolSuggestionProvider(ProtocolUtils.readString(buf)));
|
||||
@ -205,11 +202,11 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
private final byte flags;
|
||||
private final int[] children;
|
||||
private final int redirectTo;
|
||||
private final @Nullable ArgumentBuilder<Object, ?> args;
|
||||
private @MonotonicNonNull CommandNode<Object> built;
|
||||
private final @Nullable ArgumentBuilder<CommandSource, ?> args;
|
||||
private @MonotonicNonNull CommandNode<CommandSource> built;
|
||||
|
||||
private WireNode(int idx, byte flags, int[] children, int redirectTo,
|
||||
@Nullable ArgumentBuilder<Object, ?> args) {
|
||||
@Nullable ArgumentBuilder<CommandSource, ?> args) {
|
||||
this.idx = idx;
|
||||
this.flags = flags;
|
||||
this.children = children;
|
||||
@ -251,7 +248,7 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
|
||||
// If executable, add a dummy command
|
||||
if ((flags & FLAG_EXECUTABLE) != 0) {
|
||||
args.executes((Command<Object>) context -> 0);
|
||||
args.executes((Command<CommandSource>) context -> 0);
|
||||
}
|
||||
|
||||
this.built = args.build();
|
||||
@ -267,7 +264,7 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
|
||||
// Associate children with nodes
|
||||
for (int child : children) {
|
||||
CommandNode<Object> childNode = wireNodes[child].built;
|
||||
CommandNode<CommandSource> childNode = wireNodes[child].built;
|
||||
if (!(childNode instanceof RootCommandNode)) {
|
||||
built.addChild(childNode);
|
||||
}
|
||||
@ -286,9 +283,11 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
|
||||
if (args != null) {
|
||||
if (args instanceof LiteralArgumentBuilder) {
|
||||
helper.add("argsLabel", ((LiteralArgumentBuilder) args).getLiteral());
|
||||
helper.add("argsLabel",
|
||||
((LiteralArgumentBuilder<CommandSource>) args).getLiteral());
|
||||
} 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
|
||||
* name.
|
||||
*/
|
||||
public static class ProtocolSuggestionProvider implements SuggestionProvider<Object> {
|
||||
public static class ProtocolSuggestionProvider implements SuggestionProvider<CommandSource> {
|
||||
|
||||
private final String name;
|
||||
|
||||
@ -309,7 +308,7 @@ public class AvailableCommands implements MinecraftPacket {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<Suggestions> getSuggestions(CommandContext<Object> context,
|
||||
public CompletableFuture<Suggestions> getSuggestions(CommandContext<CommandSource> context,
|
||||
SuggestionsBuilder builder) throws CommandSyntaxException {
|
||||
return builder.buildFuture();
|
||||
}
|
||||
|
149
proxy/src/main/java/com/velocitypowered/proxy/util/BrigadierUtils.java
Normale Datei
149
proxy/src/main/java/com/velocitypowered/proxy/util/BrigadierUtils.java
Normale Datei
@ -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();
|
||||
}
|
||||
}
|
@ -123,6 +123,9 @@ show-ping-requests = false
|
||||
# can disable this setting to use the BungeeCord behavior.
|
||||
failover-on-unexpected-server-disconnect = true
|
||||
|
||||
# Declares the proxy commands to 1.13+ clients.
|
||||
announce-proxy-commands = true
|
||||
|
||||
# Enables the logging of commands
|
||||
log-command-executions = false
|
||||
|
||||
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
||||
}
|
||||
}
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren