New command API (#330)
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
@@ -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();
|
||||
}
|
||||
}
|
||||
|
@@ -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;
|
Reference in New Issue
Block a user