Mirror von
https://github.com/PaperMC/Velocity.git
synchronisiert 2024-11-16 21:10:30 +01:00
Add a basic command handling framework inspired by Bukkit/BungeeCord.
This doesn't yet support tab complete, that will come later. Additionally, a /server command (using your configuration) and /velocity (shows basic copyright information about the proxy) have been added.
Dieser Commit ist enthalten in:
Ursprung
09eff5a2fb
Commit
05693425bf
@ -23,6 +23,15 @@ wrapper script (`./gradlew`) as our CI builds using it.
|
||||
|
||||
It is sufficient to run `./gradlew build` to run the full build cycle.
|
||||
|
||||
## Running
|
||||
|
||||
Once you've built Velocity, you can copy and run the `-all` JAR from
|
||||
`proxy/build/libs`. Velocity will generate a default configuration file
|
||||
and you can configure it from there.
|
||||
|
||||
Alternatively, you can get the proxy JAR from the [downloads](https://www.velocitypowered.com/downloads)
|
||||
page.
|
||||
|
||||
## Status
|
||||
|
||||
Velocity is far from finished, but most of the essential pieces are in place:
|
||||
|
@ -0,0 +1,29 @@
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Represents a command that can be executed by a {@link CommandInvoker}, such as a {@link com.velocitypowered.api.proxy.Player}
|
||||
* or the console.
|
||||
*/
|
||||
public interface CommandExecutor {
|
||||
/**
|
||||
* Executes the command for the specified {@link CommandInvoker}.
|
||||
* @param invoker the invoker of this command
|
||||
* @param args the arguments for this command
|
||||
*/
|
||||
void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args);
|
||||
|
||||
/**
|
||||
* Provides tab complete suggestions for a command for a specified {@link CommandInvoker}.
|
||||
* @param invoker the invoker to run the command for
|
||||
* @param currentArgs the current, partial arguments for this command
|
||||
* @return tab complete suggestions
|
||||
*/
|
||||
default List<String> suggest(@Nonnull CommandInvoker invoker, @Nonnull String[] currentArgs) {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package com.velocitypowered.api.command;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
/**
|
||||
* Represents something that can be used to run a {@link CommandExecutor}.
|
||||
*/
|
||||
public interface CommandInvoker {
|
||||
/**
|
||||
* Sends the specified {@code component} to the invoker.
|
||||
* @param component the text component to send
|
||||
*/
|
||||
void sendMessage(@Nonnull Component component);
|
||||
|
||||
/**
|
||||
* Determines whether or not the invoker has a particular permission.
|
||||
* @param permission the permission to check for
|
||||
* @return whether or not the invoker has permission to run this command
|
||||
*/
|
||||
boolean hasPermission(@Nonnull String permission);
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
/**
|
||||
* Represents a connection to the proxy. There is no guarantee that the connection has been fully initialized.
|
||||
*/
|
||||
public interface InboundConnection {
|
||||
/**
|
||||
* Returns the player's IP address.
|
||||
* @return the player's IP
|
||||
*/
|
||||
InetSocketAddress getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Determine whether or not the player remains online.
|
||||
* @return whether or not the player active
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Returns the current protocol version this connection uses.
|
||||
* @return the protocol version the connection uses
|
||||
*/
|
||||
int getProtocolVersion();
|
||||
}
|
@ -1,18 +1,18 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.command.CommandInvoker;
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
import java.util.UUID;
|
||||
|
||||
/**
|
||||
* Represents a player who is connected to the proxy.
|
||||
*/
|
||||
public interface Player {
|
||||
public interface Player extends CommandInvoker, InboundConnection {
|
||||
/**
|
||||
* Returns the player's current username.
|
||||
* @return the username
|
||||
@ -31,18 +31,6 @@ public interface Player {
|
||||
*/
|
||||
Optional<ServerInfo> getCurrentServer();
|
||||
|
||||
/**
|
||||
* Returns the player's IP address.
|
||||
* @return the player's IP
|
||||
*/
|
||||
InetSocketAddress getRemoteAddress();
|
||||
|
||||
/**
|
||||
* Determine whether or not the player remains online.
|
||||
* @return whether or not the player active
|
||||
*/
|
||||
boolean isActive();
|
||||
|
||||
/**
|
||||
* Sends a chat message to the player's client.
|
||||
* @param component the chat message to send
|
||||
|
@ -1,5 +1,6 @@
|
||||
package com.velocitypowered.api.proxy;
|
||||
|
||||
import com.velocitypowered.api.command.CommandInvoker;
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
@ -62,4 +63,12 @@ public interface ProxyServer {
|
||||
* @param server the server to unregister
|
||||
*/
|
||||
void unregisterServer(@Nonnull ServerInfo server);
|
||||
|
||||
/**
|
||||
* Returns an instance of {@link CommandInvoker} that can be used to determine if the command is being invoked by
|
||||
* the console or a console-like executor. Plugins that execute commands are strongly urged to implement their own
|
||||
* {@link CommandInvoker} instead of using the console invoker.
|
||||
* @return the console command invoker
|
||||
*/
|
||||
CommandInvoker getConsoleCommandInvoker();
|
||||
}
|
||||
|
@ -4,19 +4,24 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import com.velocitypowered.api.command.CommandInvoker;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.network.ConnectionManager;
|
||||
import com.velocitypowered.proxy.command.ServerCommand;
|
||||
import com.velocitypowered.proxy.command.VelocityCommand;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.connection.http.NettyHttpClient;
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.command.CommandManager;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.util.ServerMap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@ -43,11 +48,26 @@ public class VelocityServer implements ProxyServer {
|
||||
private NettyHttpClient httpClient;
|
||||
private KeyPair serverKeyPair;
|
||||
private final ServerMap servers = new ServerMap();
|
||||
private final CommandManager commandManager = new CommandManager();
|
||||
|
||||
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
|
||||
private final CommandInvoker consoleCommandInvoker = new CommandInvoker() {
|
||||
@Override
|
||||
public void sendMessage(@Nonnull Component component) {
|
||||
// TODO: TerminalConsoleAppender
|
||||
logger.info(ComponentSerializers.PLAIN.serialize(component));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@Nonnull String permission) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
private VelocityServer() {
|
||||
commandManager.registerCommand("velocity", new VelocityCommand());
|
||||
commandManager.registerCommand("server", new ServerCommand());
|
||||
}
|
||||
|
||||
public static VelocityServer getServer() {
|
||||
@ -62,6 +82,10 @@ public class VelocityServer implements ProxyServer {
|
||||
return configuration;
|
||||
}
|
||||
|
||||
public CommandManager getCommandManager() {
|
||||
return commandManager;
|
||||
}
|
||||
|
||||
public void start() {
|
||||
logger.info("Using {}", Natives.compressor.getLoadedVariant());
|
||||
logger.info("Using {}", Natives.cipher.getLoadedVariant());
|
||||
@ -177,4 +201,9 @@ public class VelocityServer implements ProxyServer {
|
||||
public void unregisterServer(@Nonnull ServerInfo server) {
|
||||
servers.unregister(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandInvoker getConsoleCommandInvoker() {
|
||||
return consoleCommandInvoker;
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
package com.velocitypowered.proxy.command;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.command.CommandExecutor;
|
||||
import com.velocitypowered.api.command.CommandInvoker;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class CommandManager {
|
||||
private final Map<String, CommandExecutor> executors = new HashMap<>();
|
||||
|
||||
public void registerCommand(String name, CommandExecutor executor) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
Preconditions.checkNotNull(executor, "executor");
|
||||
this.executors.put(name, executor);
|
||||
}
|
||||
|
||||
public void unregisterCommand(String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
this.executors.remove(name);
|
||||
}
|
||||
|
||||
public boolean execute(CommandInvoker invoker, String cmdLine) {
|
||||
Preconditions.checkNotNull(invoker, "invoker");
|
||||
Preconditions.checkNotNull(cmdLine, "cmdLine");
|
||||
|
||||
String[] split = cmdLine.split(" ", -1);
|
||||
if (split.length == 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String command = split[0];
|
||||
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
|
||||
CommandExecutor executor = executors.get(command);
|
||||
if (executor == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
executor.execute(invoker, actualArgs);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to invoke command " + cmdLine + " for " + invoker, e);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
package com.velocitypowered.proxy.command;
|
||||
|
||||
import com.velocitypowered.api.command.CommandExecutor;
|
||||
import com.velocitypowered.api.command.CommandInvoker;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class ServerCommand implements CommandExecutor {
|
||||
@Override
|
||||
public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) {
|
||||
if (!(invoker instanceof Player)) {
|
||||
invoker.sendMessage(TextComponent.of("Only players may run this command.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = (Player) invoker;
|
||||
if (args.length == 1) {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<ServerInfo> server = VelocityServer.getServer().getServerInfo(serverName);
|
||||
if (!server.isPresent()) {
|
||||
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
|
||||
player.createConnectionRequest(server.get()).fireAndForget();
|
||||
} else {
|
||||
String serverList = VelocityServer.getServer().getAllServers().stream()
|
||||
.map(ServerInfo::getName)
|
||||
.collect(Collectors.joining(", "));
|
||||
player.sendMessage(TextComponent.of("Available servers: " + serverList, TextColor.YELLOW));
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
package com.velocitypowered.proxy.command;
|
||||
|
||||
import com.velocitypowered.api.command.CommandExecutor;
|
||||
import com.velocitypowered.api.command.CommandInvoker;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.event.ClickEvent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
import javax.annotation.Nonnull;
|
||||
|
||||
public class VelocityCommand implements CommandExecutor {
|
||||
@Override
|
||||
public void execute(@Nonnull CommandInvoker invoker, @Nonnull String[] args) {
|
||||
String implVersion = VelocityServer.class.getPackage().getImplementationVersion();
|
||||
TextComponent thisIsVelocity = TextComponent.builder()
|
||||
.content("This is ")
|
||||
.append(TextComponent.of("Velocity " + implVersion, TextColor.DARK_AQUA))
|
||||
.append(TextComponent.of(", the next generation Minecraft: Java Edition proxy.", TextColor.WHITE))
|
||||
.build();
|
||||
TextComponent velocityInfo = TextComponent.builder()
|
||||
.content("Copyright 2018 Velocity Contributors. Velocity is freely licensed under the terms of the " +
|
||||
"MIT License.")
|
||||
.build();
|
||||
TextComponent velocityWebsite = TextComponent.builder()
|
||||
.content("Visit the ")
|
||||
.append(TextComponent.builder("Velocity website")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://www.velocitypowered.com"))
|
||||
.build())
|
||||
.append(TextComponent.of(" or the ", TextColor.WHITE))
|
||||
.append(TextComponent.builder("Velocity GitHub")
|
||||
.color(TextColor.GREEN)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.OPEN_URL, "https://github.com/astei/velocity"))
|
||||
.build())
|
||||
.build();
|
||||
|
||||
invoker.sendMessage(thisIsVelocity);
|
||||
invoker.sendMessage(velocityInfo);
|
||||
invoker.sendMessage(velocityWebsite);
|
||||
}
|
||||
}
|
@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.backend.ServerConnection;
|
||||
import com.velocitypowered.proxy.command.VelocityCommand;
|
||||
import com.velocitypowered.api.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Objective;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Score;
|
||||
@ -9,16 +9,13 @@ import com.velocitypowered.proxy.data.scoreboard.Scoreboard;
|
||||
import com.velocitypowered.proxy.data.scoreboard.Team;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.remap.EntityIdRemapper;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import io.netty.channel.EventLoop;
|
||||
import io.netty.util.ReferenceCountUtil;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
@ -81,11 +78,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
if (packet instanceof Chat) {
|
||||
Chat chat = (Chat) packet;
|
||||
if (chat.getMessage().equals("/connect")) {
|
||||
ServerInfo info = new ServerInfo("test", new InetSocketAddress("localhost", 25566));
|
||||
player.createConnectionRequest(info).fireAndForget();
|
||||
return;
|
||||
String msg = ((Chat) packet).getMessage();
|
||||
if (msg.startsWith("/")) {
|
||||
try {
|
||||
if (!VelocityServer.getServer().getCommandManager().execute(player, msg.substring(1))) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(msg);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.info("Exception occurred while running command for {}", player.getProfile().getName(), e);
|
||||
player.sendMessage(TextComponent.of("An error occurred while running this command.", TextColor.RED));
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
player.getConnectedServer().getMinecraftConnection().write(chat);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof PluginMessage) {
|
||||
|
@ -83,6 +83,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return connection.getChannel().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return connection.getProtocolVersion();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(@Nonnull Component component, @Nonnull MessagePosition position) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
@ -217,6 +222,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return "[connected player] " + getProfile().getName() + " (" + getRemoteAddress() + ")";
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@Nonnull String permission) {
|
||||
return false; // TODO: Implement permissions.
|
||||
}
|
||||
|
||||
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
private final ServerInfo info;
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
@ -14,6 +15,8 @@ import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
private final MinecraftConnection connection;
|
||||
|
||||
@ -70,4 +73,27 @@ public class HandshakeSessionHandler implements MinecraftSessionHandler {
|
||||
connection.closeWith(LegacyDisconnect.from(TextComponent.of("Your client is old, please upgrade!", TextColor.RED)));
|
||||
}
|
||||
}
|
||||
|
||||
private static class InitialInboundConnection implements InboundConnection {
|
||||
private final MinecraftConnection connection;
|
||||
|
||||
private InitialInboundConnection(MinecraftConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getRemoteAddress() {
|
||||
return (InetSocketAddress) connection.getChannel().remoteAddress();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isActive() {
|
||||
return connection.getChannel().isActive();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getProtocolVersion() {
|
||||
return connection.getProtocolVersion();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Laden…
In neuem Issue referenzieren
Einen Benutzer sperren