merge upstream
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
package com.velocitypowered.proxy;
|
||||
|
||||
import com.velocitypowered.proxy.console.VelocityConsole;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
@@ -28,6 +27,6 @@ public class Velocity {
|
||||
|
||||
double bootTime = (System.currentTimeMillis() - startTime) / 1000d;
|
||||
logger.info("Done ({}s)!", new DecimalFormat("#.##").format(bootTime));
|
||||
new VelocityConsole(server).start();
|
||||
server.getConsoleCommandSource().start();
|
||||
}
|
||||
}
|
||||
|
@@ -4,40 +4,43 @@ 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.CommandSource;
|
||||
import com.velocitypowered.api.event.EventManager;
|
||||
import com.velocitypowered.api.event.proxy.ProxyInitializeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyShutdownEvent;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.proxy.command.ServerCommand;
|
||||
import com.velocitypowered.proxy.command.ShutdownCommand;
|
||||
import com.velocitypowered.proxy.command.VelocityCommand;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.network.http.NettyHttpClient;
|
||||
import com.velocitypowered.proxy.command.VelocityCommandManager;
|
||||
import com.velocitypowered.proxy.config.AnnotatedConfig;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.console.VelocityConsole;
|
||||
import com.velocitypowered.proxy.messages.VelocityChannelRegistrar;
|
||||
import com.velocitypowered.proxy.network.ConnectionManager;
|
||||
import com.velocitypowered.proxy.network.http.NettyHttpClient;
|
||||
import com.velocitypowered.proxy.plugin.VelocityEventManager;
|
||||
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
|
||||
import com.velocitypowered.proxy.plugin.VelocityPluginManager;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.protocol.util.FaviconSerializer;
|
||||
import com.velocitypowered.proxy.scheduler.VelocityScheduler;
|
||||
import com.velocitypowered.proxy.server.ServerMap;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import com.velocitypowered.proxy.util.Ratelimiter;
|
||||
import com.velocitypowered.proxy.util.ServerMap;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import net.kyori.text.serializer.GsonComponentSerializer;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
@@ -61,7 +64,7 @@ public class VelocityServer implements ProxyServer {
|
||||
private VelocityConfiguration configuration;
|
||||
private NettyHttpClient httpClient;
|
||||
private KeyPair serverKeyPair;
|
||||
private final ServerMap servers = new ServerMap();
|
||||
private final ServerMap servers = new ServerMap(this);
|
||||
private final VelocityCommandManager commandManager = new VelocityCommandManager();
|
||||
private final AtomicBoolean shutdownInProgress = new AtomicBoolean(false);
|
||||
private boolean shutdown = false;
|
||||
@@ -69,17 +72,7 @@ public class VelocityServer implements ProxyServer {
|
||||
|
||||
private final Map<UUID, ConnectedPlayer> connectionsByUuid = new ConcurrentHashMap<>();
|
||||
private final Map<String, ConnectedPlayer> connectionsByName = new ConcurrentHashMap<>();
|
||||
private final CommandSource consoleCommandSource = new CommandSource() {
|
||||
@Override
|
||||
public void sendMessage(Component component) {
|
||||
logger.info(ComponentSerializers.LEGACY.serialize(component));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
private final VelocityConsole console = new VelocityConsole(this);
|
||||
private Ratelimiter ipAttemptLimiter;
|
||||
private VelocityEventManager eventManager;
|
||||
private VelocityScheduler scheduler;
|
||||
@@ -144,6 +137,9 @@ public class VelocityServer implements ProxyServer {
|
||||
// issues) and there is almost no chance ExecutionException will be thrown.
|
||||
}
|
||||
|
||||
// init console permissions after plugins are loaded
|
||||
console.setupPermissions();
|
||||
|
||||
this.cm.bind(configuration.getBind());
|
||||
|
||||
if (configuration.isQueryEnabled()) {
|
||||
@@ -252,6 +248,15 @@ public class VelocityServer implements ProxyServer {
|
||||
return Optional.ofNullable(connectionsByUuid.get(uuid));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void broadcast(Component component) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
Chat chat = Chat.createClientbound(component);
|
||||
for (ConnectedPlayer player : connectionsByUuid.values()) {
|
||||
player.getConnection().write(chat);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getAllPlayers() {
|
||||
return ImmutableList.copyOf(connectionsByUuid.values());
|
||||
@@ -263,19 +268,19 @@ public class VelocityServer implements ProxyServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<ServerInfo> getServerInfo(String name) {
|
||||
public Optional<RegisteredServer> getServer(String name) {
|
||||
Preconditions.checkNotNull(name, "name");
|
||||
return servers.getServer(name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<ServerInfo> getAllServers() {
|
||||
public Collection<RegisteredServer> getAllServers() {
|
||||
return servers.getAllServers();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void registerServer(ServerInfo server) {
|
||||
servers.register(server);
|
||||
public RegisteredServer registerServer(ServerInfo server) {
|
||||
return servers.register(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -284,8 +289,8 @@ public class VelocityServer implements ProxyServer {
|
||||
}
|
||||
|
||||
@Override
|
||||
public CommandSource getConsoleCommandSource() {
|
||||
return consoleCommandSource;
|
||||
public VelocityConsole getConsoleCommandSource() {
|
||||
return console;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -3,14 +3,17 @@ 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.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.event.ClickEvent;
|
||||
import net.kyori.text.event.HoverEvent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
@@ -34,7 +37,7 @@ public class ServerCommand implements Command {
|
||||
if (args.length == 1) {
|
||||
// Trying to connect to a server.
|
||||
String serverName = args[0];
|
||||
Optional<ServerInfo> toConnect = server.getServerInfo(serverName);
|
||||
Optional<RegisteredServer> toConnect = server.getServer(serverName);
|
||||
if (!toConnect.isPresent()) {
|
||||
player.sendMessage(TextComponent.of("Server " + serverName + " doesn't exist.", TextColor.RED));
|
||||
return;
|
||||
@@ -48,17 +51,19 @@ public class ServerCommand implements Command {
|
||||
|
||||
// Assemble the list of servers as components
|
||||
TextComponent.Builder serverListBuilder = TextComponent.builder("Available servers: ").color(TextColor.YELLOW);
|
||||
List<ServerInfo> infos = ImmutableList.copyOf(server.getAllServers());
|
||||
List<RegisteredServer> infos = ImmutableList.copyOf(server.getAllServers());
|
||||
for (int i = 0; i < infos.size(); i++) {
|
||||
ServerInfo serverInfo = infos.get(i);
|
||||
TextComponent infoComponent = TextComponent.of(serverInfo.getName());
|
||||
if (serverInfo.getName().equals(currentServer)) {
|
||||
RegisteredServer rs = infos.get(i);
|
||||
TextComponent infoComponent = TextComponent.of(rs.getServerInfo().getName());
|
||||
String playersText = rs.getPlayersConnected().size() + " player(s) online";
|
||||
if (rs.getServerInfo().getName().equals(currentServer)) {
|
||||
infoComponent = infoComponent.color(TextColor.GREEN)
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Currently connected to this server")));
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT,
|
||||
TextComponent.of("Currently connected to this server\n" + playersText)));
|
||||
} else {
|
||||
infoComponent = infoComponent.color(TextColor.GRAY)
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + serverInfo.getName()))
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server")));
|
||||
.clickEvent(new ClickEvent(ClickEvent.Action.RUN_COMMAND, "/server " + rs.getServerInfo().getName()))
|
||||
.hoverEvent(new HoverEvent(HoverEvent.Action.SHOW_TEXT, TextComponent.of("Click to connect to this server\n" + playersText)));
|
||||
}
|
||||
serverListBuilder.append(infoComponent);
|
||||
if (i != infos.size() - 1) {
|
||||
@@ -74,15 +79,20 @@ public class ServerCommand implements Command {
|
||||
public List<String> suggest(CommandSource source, String[] currentArgs) {
|
||||
if (currentArgs.length == 0) {
|
||||
return server.getAllServers().stream()
|
||||
.map(ServerInfo::getName)
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.collect(Collectors.toList());
|
||||
} else if (currentArgs.length == 1) {
|
||||
return server.getAllServers().stream()
|
||||
.map(ServerInfo::getName)
|
||||
.map(rs -> rs.getServerInfo().getName())
|
||||
.filter(name -> name.regionMatches(true, 0, currentArgs[0], 0, currentArgs[0].length()))
|
||||
.collect(Collectors.toList());
|
||||
} else {
|
||||
return ImmutableList.of();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
|
||||
return source.getPermissionValue("velocity.command.server") != Tristate.FALSE;
|
||||
}
|
||||
}
|
||||
|
@@ -5,6 +5,7 @@ import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public class ShutdownCommand implements Command {
|
||||
private final VelocityServer server;
|
||||
@@ -21,4 +22,9 @@ public class ShutdownCommand implements Command {
|
||||
}
|
||||
server.shutdown();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
|
||||
return source == server.getConsoleCommandSource();
|
||||
}
|
||||
}
|
||||
|
@@ -2,10 +2,12 @@ package com.velocitypowered.proxy.command;
|
||||
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.event.ClickEvent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
public class VelocityCommand implements Command {
|
||||
@Override
|
||||
@@ -37,4 +39,9 @@ public class VelocityCommand implements Command {
|
||||
source.sendMessage(velocityInfo);
|
||||
source.sendMessage(velocityWebsite);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(@NonNull CommandSource source, @NonNull String[] args) {
|
||||
return source.getPermissionValue("velocity.command.info") != Tristate.FALSE;
|
||||
}
|
||||
}
|
||||
|
@@ -1,10 +1,9 @@
|
||||
package com.velocitypowered.proxy.command;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.command.Command;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.command.CommandManager;
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
@@ -47,6 +46,10 @@ public class VelocityCommandManager implements CommandManager {
|
||||
}
|
||||
|
||||
try {
|
||||
if (!command.hasPermission(source, actualArgs)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
command.execute(source, actualArgs);
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
@@ -63,23 +66,29 @@ public class VelocityCommandManager implements CommandManager {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
String command = split[0];
|
||||
String alias = split[0];
|
||||
if (split.length == 1) {
|
||||
return Optional.of(commands.keySet().stream()
|
||||
.filter(cmd -> cmd.regionMatches(true, 0, command, 0, command.length()))
|
||||
return Optional.of(commands.entrySet().stream()
|
||||
.filter(ent -> ent.getKey().regionMatches(true, 0, alias, 0, alias.length()))
|
||||
.filter(ent -> ent.getValue().hasPermission(source, new String[0]))
|
||||
.map(ent -> "/" + ent.getKey())
|
||||
.collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
String[] actualArgs = Arrays.copyOfRange(split, 1, split.length);
|
||||
Command executor = commands.get(command);
|
||||
if (executor == null) {
|
||||
Command command = commands.get(alias.toLowerCase(Locale.ENGLISH));
|
||||
if (command == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
try {
|
||||
return Optional.of(executor.suggest(source, actualArgs));
|
||||
if (!command.hasPermission(source, actualArgs)) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
return Optional.of(command.suggest(source, actualArgs));
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException("Unable to invoke suggestions for command " + command + " for " + source, e);
|
||||
throw new RuntimeException("Unable to invoke suggestions for command " + alias + " for " + source, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,5 +1,8 @@
|
||||
package com.velocitypowered.proxy.config;
|
||||
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.annotation.ElementType;
|
||||
@@ -8,18 +11,12 @@ import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
import java.lang.reflect.Field;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.AtomicMoveNotSupportedException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.nio.file.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
/**
|
||||
* Only for simple configs
|
||||
|
@@ -5,10 +5,9 @@ import com.google.common.collect.ImmutableMap;
|
||||
import com.moandjiezana.toml.Toml;
|
||||
import com.velocitypowered.api.util.Favicon;
|
||||
import com.velocitypowered.proxy.util.AddressUtil;
|
||||
import com.velocitypowered.api.util.LegacyChatColorUtils;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
@@ -17,12 +16,7 @@ import java.nio.charset.StandardCharsets;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Random;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import java.util.*;
|
||||
|
||||
public class VelocityConfiguration extends AnnotatedConfig {
|
||||
|
||||
@@ -273,6 +267,14 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return announceForge;
|
||||
}
|
||||
|
||||
public int getConnectTimeout() {
|
||||
return advanced.getConnectionTimeout();
|
||||
}
|
||||
|
||||
public int getReadTimeout() {
|
||||
return advanced.getReadTimeout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return MoreObjects.toStringHelper(this)
|
||||
@@ -420,21 +422,23 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
"Disable by setting to 0"})
|
||||
@ConfigKey("login-ratelimit")
|
||||
private int loginRatelimit = 3000;
|
||||
@Comment({"Specify a custom timeout for connection timeouts here. The default is five seconds."})
|
||||
@ConfigKey("connection-timeout")
|
||||
private int connectionTimeout = 5000;
|
||||
@Comment({"Specify a read timeout for connections here. The default is 30 seconds."})
|
||||
@ConfigKey("read-timeout")
|
||||
private int readTimeout = 30000;
|
||||
|
||||
private Advanced() {
|
||||
}
|
||||
|
||||
private Advanced(int compressionThreshold, int compressionLevel, int loginRatelimit) {
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
this.compressionLevel = compressionLevel;
|
||||
this.loginRatelimit = loginRatelimit;
|
||||
}
|
||||
|
||||
private Advanced(Toml toml) {
|
||||
if (toml != null) {
|
||||
this.compressionThreshold = toml.getLong("compression-threshold", 1024L).intValue();
|
||||
this.compressionLevel = toml.getLong("compression-level", -1L).intValue();
|
||||
this.loginRatelimit = toml.getLong("login-ratelimit", 3000L).intValue();
|
||||
this.connectionTimeout = toml.getLong("connection-timeout", 5000L).intValue();
|
||||
this.readTimeout = toml.getLong("read-timeout", 30000L).intValue();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -442,33 +446,31 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return compressionThreshold;
|
||||
}
|
||||
|
||||
public void setCompressionThreshold(int compressionThreshold) {
|
||||
this.compressionThreshold = compressionThreshold;
|
||||
}
|
||||
|
||||
public int getCompressionLevel() {
|
||||
return compressionLevel;
|
||||
}
|
||||
|
||||
public void setCompressionLevel(int compressionLevel) {
|
||||
this.compressionLevel = compressionLevel;
|
||||
}
|
||||
|
||||
public int getLoginRatelimit() {
|
||||
return loginRatelimit;
|
||||
}
|
||||
|
||||
public void setLoginRatelimit(int loginRatelimit) {
|
||||
this.loginRatelimit = loginRatelimit;
|
||||
public int getConnectionTimeout() {
|
||||
return connectionTimeout;
|
||||
}
|
||||
|
||||
public int getReadTimeout() {
|
||||
return readTimeout;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Advanced{"
|
||||
+ "compressionThreshold=" + compressionThreshold
|
||||
+ ", compressionLevel=" + compressionLevel
|
||||
+ ", loginRatelimit=" + loginRatelimit
|
||||
+ '}';
|
||||
return "Advanced{" +
|
||||
"compressionThreshold=" + compressionThreshold +
|
||||
", compressionLevel=" + compressionLevel +
|
||||
", loginRatelimit=" + loginRatelimit +
|
||||
", connectionTimeout=" + connectionTimeout +
|
||||
", readTimeout=" + readTimeout +
|
||||
'}';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -500,18 +502,10 @@ public class VelocityConfiguration extends AnnotatedConfig {
|
||||
return queryEnabled;
|
||||
}
|
||||
|
||||
public void setQueryEnabled(boolean queryEnabled) {
|
||||
this.queryEnabled = queryEnabled;
|
||||
}
|
||||
|
||||
public int getQueryPort() {
|
||||
return queryPort;
|
||||
}
|
||||
|
||||
public void setQueryPort(int queryPort) {
|
||||
this.queryPort = queryPort;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Query{"
|
||||
|
@@ -2,13 +2,13 @@ package com.velocitypowered.proxy.connection;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipherFactory;
|
||||
import com.velocitypowered.natives.util.Natives;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.natives.encryption.VelocityCipher;
|
||||
import com.velocitypowered.proxy.protocol.netty.*;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.Channel;
|
||||
@@ -21,17 +21,9 @@ import org.apache.logging.log4j.Logger;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
import java.security.GeneralSecurityException;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.CIPHER_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.CIPHER_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.COMPRESSION_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
/**
|
||||
* A utility class to make working with the pipeline a little less painful and transparently handles certain Minecraft
|
||||
@@ -99,8 +91,6 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
|
||||
|
||||
if (association != null) {
|
||||
logger.error("{}: exception encountered", association, cause);
|
||||
} else {
|
||||
logger.error("{} encountered an exception", ctx.channel().remoteAddress(), cause);
|
||||
}
|
||||
|
||||
ctx.close();
|
||||
|
@@ -8,6 +8,8 @@ public class VelocityConstants {
|
||||
public static final String VELOCITY_IP_FORWARDING_CHANNEL = "velocity:player_info";
|
||||
|
||||
public static final String FORGE_LEGACY_HANDSHAKE_CHANNEL = "FML|HS";
|
||||
public static final String FORGE_LEGACY_CHANNEL = "FML";
|
||||
public static final String FORGE_MULTIPART_LEGACY_CHANNEL = "FML|MP";
|
||||
|
||||
public static final byte[] FORGE_LEGACY_HANDSHAKE_RESET_DATA = new byte[] { -2, 0 };
|
||||
}
|
||||
|
@@ -1,15 +1,16 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.event.player.ServerConnectedEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelSide;
|
||||
import com.velocitypowered.api.proxy.messages.MessageHandler;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
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.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
@@ -24,7 +25,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServerInfo()));
|
||||
server.getEventManager().fireAndForget(new ServerConnectedEvent(connection.getPlayer(), connection.getServer()));
|
||||
connection.getServer().addPlayer(connection.getPlayer());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -32,7 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (!connection.getPlayer().isActive()) {
|
||||
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
|
||||
// errors.
|
||||
connection.getMinecraftConnection().close();
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -40,11 +42,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
(ClientPlaySessionHandler) connection.getPlayer().getConnection().getSessionHandler();
|
||||
if (packet instanceof KeepAlive) {
|
||||
// Forward onto the player
|
||||
playerHandler.setLastPing(((KeepAlive) packet).getRandomId());
|
||||
connection.setLastPingId(((KeepAlive) packet).getRandomId());
|
||||
connection.getPlayer().getConnection().write(packet);
|
||||
} else if (packet instanceof Disconnect) {
|
||||
Disconnect original = (Disconnect) packet;
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), original);
|
||||
connection.disconnect();
|
||||
connection.getPlayer().handleConnectionException(connection.getServer(), original);
|
||||
} else if (packet instanceof JoinGame) {
|
||||
playerHandler.handleBackendJoinGame((JoinGame) packet);
|
||||
} else if (packet instanceof BossBar) {
|
||||
@@ -83,11 +86,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(connection,
|
||||
ChannelSide.FROM_SERVER, pm);
|
||||
if (status == MessageHandler.ForwardStatus.FORWARD) {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(pm.getChannel());
|
||||
if (id == null) {
|
||||
connection.getPlayer().getConnection().write(pm);
|
||||
} else {
|
||||
PluginMessageEvent event = new PluginMessageEvent(connection, connection.getPlayer(), id, pm.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
connection.getPlayer().getConnection().write(pm);
|
||||
}
|
||||
}, connection.getMinecraftConnection().getChannel().eventLoop());
|
||||
}
|
||||
} else if (packet instanceof TabCompleteResponse) {
|
||||
playerHandler.handleTabCompleteResponse((TabCompleteResponse) packet);
|
||||
} else if (connection.hasCompletedJoin()) {
|
||||
// Just forward the packet on. We don't have anything to handle at this time.
|
||||
connection.getPlayer().getConnection().write(packet);
|
||||
@@ -99,7 +111,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
if (!connection.getPlayer().isActive()) {
|
||||
// Connection was left open accidentally. Close it so as to avoid "You logged in from another location"
|
||||
// errors.
|
||||
connection.getMinecraftConnection().close();
|
||||
connection.disconnect();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -110,7 +122,20 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
connection.getPlayer().handleConnectionException(connection.getServerInfo(), throwable);
|
||||
connection.getPlayer().handleConnectionException(connection.getServer(), throwable);
|
||||
}
|
||||
|
||||
public VelocityServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
connection.getServer().removePlayer(connection.getPlayer());
|
||||
if (!connection.isGracefulDisconnect()) {
|
||||
connection.getPlayer().handleConnectionException(connection.getServer(), Disconnect.create(
|
||||
ConnectionMessages.UNEXPECTED_DISCONNECT));
|
||||
}
|
||||
}
|
||||
|
||||
private boolean canForwardPluginMessage(PluginMessage message) {
|
||||
|
@@ -1,18 +1,18 @@
|
||||
package com.velocitypowered.proxy.connection.backend;
|
||||
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.Unpooled;
|
||||
import net.kyori.text.TextComponent;
|
||||
@@ -82,12 +82,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
if (existingConnection == null) {
|
||||
// Strap on the play session handler
|
||||
connection.getPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(server, connection.getPlayer()));
|
||||
|
||||
// This is for legacy Forge servers - during first connection the FML handshake will transition to complete regardless
|
||||
// Thus, we need to ensure that a reset packet is ALWAYS sent on first switch.
|
||||
//
|
||||
// The call will handle if the player is not a Forge player appropriately.
|
||||
connection.getPlayer().getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
} else {
|
||||
// The previous server connection should become obsolete.
|
||||
// Before we remove it, if the server we are departing is modded, we must always reset the client state.
|
||||
|
@@ -4,9 +4,14 @@ import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
@@ -14,12 +19,11 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import io.netty.channel.*;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
import io.netty.util.AttributeKey;
|
||||
|
||||
@@ -27,27 +31,24 @@ import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.proxy.VelocityServer.GSON;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.FRAME_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.HANDLER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_DECODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.MINECRAFT_ENCODER;
|
||||
import static com.velocitypowered.proxy.network.Connections.READ_TIMEOUT;
|
||||
import static com.velocitypowered.proxy.network.Connections.SERVER_READ_TIMEOUT_SECONDS;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
public class VelocityServerConnection implements MinecraftConnectionAssociation, ServerConnection {
|
||||
static final AttributeKey<CompletableFuture<ConnectionRequestBuilder.Result>> CONNECTION_NOTIFIER =
|
||||
AttributeKey.newInstance("connection-notification-result");
|
||||
|
||||
private final ServerInfo serverInfo;
|
||||
private final VelocityRegisteredServer registeredServer;
|
||||
private final ConnectedPlayer proxyPlayer;
|
||||
private final VelocityServer server;
|
||||
private MinecraftConnection minecraftConnection;
|
||||
private boolean legacyForge = false;
|
||||
private boolean hasCompletedJoin = false;
|
||||
private boolean gracefulDisconnect = false;
|
||||
private long lastPingId;
|
||||
private long lastPingSent;
|
||||
|
||||
public VelocityServerConnection(ServerInfo target, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.serverInfo = target;
|
||||
public VelocityServerConnection(VelocityRegisteredServer registeredServer, ConnectedPlayer proxyPlayer, VelocityServer server) {
|
||||
this.registeredServer = registeredServer;
|
||||
this.proxyPlayer = proxyPlayer;
|
||||
this.server = server;
|
||||
}
|
||||
@@ -55,12 +56,11 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
public CompletableFuture<ConnectionRequestBuilder.Result> connect() {
|
||||
CompletableFuture<ConnectionRequestBuilder.Result> result = new CompletableFuture<>();
|
||||
server.initializeGenericBootstrap()
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(SERVER_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
|
||||
@@ -73,7 +73,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
ch.pipeline().addLast(HANDLER, connection);
|
||||
}
|
||||
})
|
||||
.connect(serverInfo.getAddress())
|
||||
.connect(registeredServer.getServerInfo().getAddress())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
@@ -95,7 +95,7 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
// BungeeCord IP forwarding is simply a special injection after the "address" in the handshake,
|
||||
// separated by \0 (the null byte). In order, you send the original host, the player's IP, their
|
||||
// UUID (undashed), and if you are in online-mode, their login properties (retrieved from Mojang).
|
||||
return serverInfo.getAddress().getHostString() + "\0" +
|
||||
return registeredServer.getServerInfo().getAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getRemoteAddress().getHostString() + "\0" +
|
||||
proxyPlayer.getProfile().getId() + "\0" +
|
||||
GSON.toJson(proxyPlayer.getProfile().getProperties());
|
||||
@@ -113,9 +113,9 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
} else if (proxyPlayer.getConnection().isLegacyForge()) {
|
||||
handshake.setServerAddress(handshake.getServerAddress() + "\0FML\0");
|
||||
} else {
|
||||
handshake.setServerAddress(serverInfo.getAddress().getHostString());
|
||||
handshake.setServerAddress(registeredServer.getServerInfo().getAddress().getHostString());
|
||||
}
|
||||
handshake.setPort(serverInfo.getAddress().getPort());
|
||||
handshake.setPort(registeredServer.getServerInfo().getAddress().getPort());
|
||||
minecraftConnection.write(handshake);
|
||||
|
||||
int protocolVersion = proxyPlayer.getConnection().getProtocolVersion();
|
||||
@@ -127,12 +127,24 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
minecraftConnection.write(login);
|
||||
}
|
||||
|
||||
public void writeIfJoined(PluginMessage message) {
|
||||
if (hasCompletedJoin) {
|
||||
minecraftConnection.write(message);
|
||||
}
|
||||
}
|
||||
|
||||
public MinecraftConnection getMinecraftConnection() {
|
||||
return minecraftConnection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public VelocityRegisteredServer getServer() {
|
||||
return registeredServer;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverInfo;
|
||||
return registeredServer.getServerInfo();
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -141,23 +153,27 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
}
|
||||
|
||||
public void disconnect() {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
if (minecraftConnection != null) {
|
||||
minecraftConnection.close();
|
||||
minecraftConnection = null;
|
||||
gracefulDisconnect = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + serverInfo.getName();
|
||||
return "[server connection] " + proxyPlayer.getProfile().getName() + " -> " + registeredServer.getServerInfo().getName();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(identifier.getId());
|
||||
message.setData(data);
|
||||
minecraftConnection.write(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean isLegacyForge() {
|
||||
@@ -175,4 +191,25 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
|
||||
public void setHasCompletedJoin(boolean hasCompletedJoin) {
|
||||
this.hasCompletedJoin = hasCompletedJoin;
|
||||
}
|
||||
|
||||
public boolean isGracefulDisconnect() {
|
||||
return gracefulDisconnect;
|
||||
}
|
||||
|
||||
public long getLastPingId() {
|
||||
return lastPingId;
|
||||
}
|
||||
|
||||
public long getLastPingSent() {
|
||||
return lastPingSent;
|
||||
}
|
||||
|
||||
public void setLastPingId(long lastPingId) {
|
||||
this.lastPingId = lastPingId;
|
||||
this.lastPingSent = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
public void resetLastPingId() {
|
||||
this.lastPingId = -1;
|
||||
}
|
||||
}
|
||||
|
@@ -1,14 +1,15 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.api.event.connection.DisconnectEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelSide;
|
||||
import com.velocitypowered.api.proxy.messages.MessageHandler;
|
||||
import com.velocitypowered.api.event.connection.PluginMessageEvent;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.util.PluginMessageUtil;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
@@ -28,12 +29,12 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
private static final int MAX_PLUGIN_CHANNELS = 1024;
|
||||
|
||||
private final ConnectedPlayer player;
|
||||
private long lastPingID = -1;
|
||||
private long lastPingSent = -1;
|
||||
private boolean spawned = false;
|
||||
private final List<UUID> serverBossBars = new ArrayList<>();
|
||||
private final Set<String> clientPluginMsgChannels = new HashSet<>();
|
||||
private final Queue<PluginMessage> loginPluginMessages = new ArrayDeque<>();
|
||||
private final VelocityServer server;
|
||||
private TabCompleteRequest outstandingTabComplete;
|
||||
|
||||
public ClientPlaySessionHandler(VelocityServer server, ConnectedPlayer player) {
|
||||
this.player = player;
|
||||
@@ -53,16 +54,22 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet instanceof KeepAlive) {
|
||||
KeepAlive keepAlive = (KeepAlive) packet;
|
||||
if (keepAlive.getRandomId() != lastPingID) {
|
||||
if (keepAlive.getRandomId() != serverConnection.getLastPingId()) {
|
||||
// The last keep alive we got was probably from a different server. Let's ignore it, and hope the next
|
||||
// ping is alright.
|
||||
return;
|
||||
}
|
||||
player.setPing(System.currentTimeMillis() - lastPingSent);
|
||||
resetPingData();
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
player.setPing(System.currentTimeMillis() - serverConnection.getLastPingSent());
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
serverConnection.resetLastPingId();
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -92,31 +99,9 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
if (packet instanceof TabCompleteRequest) {
|
||||
TabCompleteRequest req = (TabCompleteRequest) packet;
|
||||
int lastSpace = req.getCommand().indexOf(' ');
|
||||
if (!req.isAssumeCommand() && lastSpace != -1) {
|
||||
String command = req.getCommand().substring(1);
|
||||
try {
|
||||
Optional<List<String>> offers = server.getCommandManager().offerSuggestions(player, command);
|
||||
if (offers.isPresent()) {
|
||||
TabCompleteResponse response = new TabCompleteResponse();
|
||||
response.setTransactionId(req.getTransactionId());
|
||||
response.setStart(lastSpace);
|
||||
response.setLength(req.getCommand().length() - lastSpace);
|
||||
|
||||
for (String s : offers.get()) {
|
||||
response.getOffers().add(new TabCompleteResponse.Offer(s, null));
|
||||
}
|
||||
|
||||
player.getConnection().write(response);
|
||||
} else {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
}
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for " + player.getUsername() + " for command '" + req.getCommand() + "'", e);
|
||||
}
|
||||
return;
|
||||
}
|
||||
// Record the request so that the outstanding request can be augmented later.
|
||||
outstandingTabComplete = (TabCompleteRequest) packet;
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
}
|
||||
|
||||
if (packet instanceof PluginMessage) {
|
||||
@@ -125,15 +110,21 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
// If we don't want to handle this packet, just forward it on.
|
||||
if (player.getConnectedServer().hasCompletedJoin()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
if (serverConnection.hasCompletedJoin()) {
|
||||
serverConnection.getMinecraftConnection().write(packet);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleUnknown(ByteBuf buf) {
|
||||
if (player.getConnectedServer().hasCompletedJoin()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(buf.retain());
|
||||
VelocityServerConnection serverConnection = player.getConnectedServer();
|
||||
if (serverConnection == null) {
|
||||
// No server connection yet, probably transitioning.
|
||||
return;
|
||||
}
|
||||
|
||||
if (serverConnection.hasCompletedJoin()) {
|
||||
serverConnection.getMinecraftConnection().write(buf.retain());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -162,11 +153,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
public void handleBackendJoinGame(JoinGame joinGame) {
|
||||
resetPingData(); // reset ping data;
|
||||
if (!spawned) {
|
||||
// nothing special to do here
|
||||
// Nothing special to do with regards to spawning the player
|
||||
spawned = true;
|
||||
player.getConnection().delayedWrite(joinGame);
|
||||
|
||||
// We have something special to do for legacy Forge servers - during first connection the FML handshake
|
||||
// will transition to complete regardless. Thus, we need to ensure that a reset packet is ALWAYS sent on
|
||||
// first switch.
|
||||
//
|
||||
// As we know that calling this branch only happens on first join, we set that if we are a Forge
|
||||
// client that we must reset on the next switch.
|
||||
//
|
||||
// The call will handle if the player is not a Forge player appropriately.
|
||||
player.getConnection().setCanSendLegacyFMLResetPacket(true);
|
||||
} else {
|
||||
// Ah, this is the meat and potatoes of the whole venture!
|
||||
//
|
||||
@@ -210,6 +210,15 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
channel, toRegister));
|
||||
}
|
||||
|
||||
// If we had plugin messages queued during login/FML handshake, send them now.
|
||||
PluginMessage pm;
|
||||
while ((pm = loginPluginMessages.poll()) != null) {
|
||||
player.getConnectedServer().getMinecraftConnection().delayedWrite(pm);
|
||||
}
|
||||
|
||||
// Clear any title from the previous server.
|
||||
player.getConnection().delayedWrite(TitlePacket.resetForProtocolVersion(player.getProtocolVersion()));
|
||||
|
||||
// Flush everything
|
||||
player.getConnection().flush();
|
||||
player.getConnectedServer().getMinecraftConnection().flush();
|
||||
@@ -234,8 +243,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return serverBossBars;
|
||||
}
|
||||
|
||||
public void handleClientPluginMessage(PluginMessage packet) {
|
||||
if (packet.getChannel().equals("REGISTER") || packet.getChannel().equals("minecraft:register")) {
|
||||
private void handleClientPluginMessage(PluginMessage packet) {
|
||||
if (PluginMessageUtil.isMCRegister(packet)) {
|
||||
List<String> actuallyRegistered = new ArrayList<>();
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
for (String channel : channels) {
|
||||
@@ -252,31 +261,35 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
PluginMessage newRegisterPacket = PluginMessageUtil.constructChannelsPacket(packet.getChannel(), actuallyRegistered);
|
||||
player.getConnectedServer().getMinecraftConnection().write(newRegisterPacket);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (packet.getChannel().equals("UNREGISTER") || packet.getChannel().equals("minecraft:unregister")) {
|
||||
} else if (PluginMessageUtil.isMCUnregister(packet)) {
|
||||
List<String> channels = PluginMessageUtil.getChannels(packet);
|
||||
clientPluginMsgChannels.removeAll(channels);
|
||||
}
|
||||
|
||||
if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else if (PluginMessageUtil.isMCBrand(packet)) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(PluginMessageUtil.rewriteMCBrand(packet));
|
||||
return;
|
||||
}
|
||||
|
||||
if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
||||
// Ensure that the messages are forwarded
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
return;
|
||||
}
|
||||
|
||||
MessageHandler.ForwardStatus status = server.getChannelRegistrar().handlePluginMessage(player,
|
||||
ChannelSide.FROM_CLIENT, packet);
|
||||
if (status == MessageHandler.ForwardStatus.FORWARD) {
|
||||
// We're going to forward on the original packet.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else if (player.getConnectedServer().isLegacyForge() && !player.getConnectedServer().hasCompletedJoin()) {
|
||||
if (packet.getChannel().equals(VelocityConstants.FORGE_LEGACY_HANDSHAKE_CHANNEL)) {
|
||||
// Always forward the FML handshake to the remote server.
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else {
|
||||
// The client is trying to send messages too early. This is primarily caused by mods, but it's further
|
||||
// aggravated by Velocity. To work around these issues, we will queue any non-FML handshake messages to
|
||||
// be sent once the JoinGame packet has been received by the proxy.
|
||||
loginPluginMessages.add(packet);
|
||||
}
|
||||
} else {
|
||||
ChannelIdentifier id = server.getChannelRegistrar().getFromId(packet.getChannel());
|
||||
if (id == null) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
} else {
|
||||
PluginMessageEvent event = new PluginMessageEvent(player, player.getConnectedServer(), id, packet.getData());
|
||||
server.getEventManager().fire(event)
|
||||
.thenAcceptAsync(pme -> {
|
||||
if (pme.getResult().isAllowed()) {
|
||||
player.getConnectedServer().getMinecraftConnection().write(packet);
|
||||
}
|
||||
}, player.getConnectedServer().getMinecraftConnection().getChannel().eventLoop());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,13 +297,20 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
|
||||
return clientPluginMsgChannels;
|
||||
}
|
||||
|
||||
public void setLastPing(long lastPing) {
|
||||
this.lastPingID = lastPing;
|
||||
this.lastPingSent = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
private void resetPingData() {
|
||||
this.lastPingID = -1;
|
||||
this.lastPingSent = -1;
|
||||
public void handleTabCompleteResponse(TabCompleteResponse response) {
|
||||
if (outstandingTabComplete != null) {
|
||||
if (!outstandingTabComplete.isAssumeCommand()) {
|
||||
String command = outstandingTabComplete.getCommand().substring(1);
|
||||
try {
|
||||
Optional<List<String>> offers = server.getCommandManager().offerSuggestions(player, command);
|
||||
offers.ifPresent(strings -> response.getOffers().addAll(strings));
|
||||
} catch (Exception e) {
|
||||
logger.error("Unable to provide tab list completions for {} for command '{}'", player.getUsername(),
|
||||
command, e);
|
||||
}
|
||||
outstandingTabComplete = null;
|
||||
}
|
||||
player.getConnection().write(response);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -3,6 +3,7 @@ package com.velocitypowered.proxy.connection.client;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.player.SkinParts;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
public class ClientSettingsWrapper implements PlayerSettings {
|
||||
|
@@ -7,28 +7,29 @@ import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
|
||||
import com.velocitypowered.api.event.player.ServerPreConnectEvent;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
import com.velocitypowered.api.permission.PermissionProvider;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.api.proxy.ConnectionRequestBuilder;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.player.PlayerSettings;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.api.util.MessagePosition;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.util.title.TextTitle;
|
||||
import com.velocitypowered.api.util.title.Title;
|
||||
import com.velocitypowered.api.util.title.Titles;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
|
||||
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.protocol.packet.Chat;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
|
||||
import com.velocitypowered.proxy.protocol.packet.ClientSettings;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
|
||||
import com.velocitypowered.proxy.util.ThrowableUtils;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.protocol.packet.Disconnect;
|
||||
import com.velocitypowered.proxy.protocol.packet.HeaderAndFooter;
|
||||
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.TranslatableComponent;
|
||||
@@ -143,11 +144,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
byte pos = (byte) position.ordinal();
|
||||
String json;
|
||||
if (position == MessagePosition.ACTION_BAR) {
|
||||
// Due to issues with action bar packets, we'll need to convert the text message into a legacy message
|
||||
// and then inject the legacy text into a component... yuck!
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("text", ComponentSerializers.LEGACY.serialize(component));
|
||||
json = VelocityServer.GSON.toJson(object);
|
||||
if (getProtocolVersion() >= ProtocolConstants.MINECRAFT_1_11) {
|
||||
// We can use the title packet instead.
|
||||
TitlePacket pkt = new TitlePacket();
|
||||
pkt.setAction(TitlePacket.SET_ACTION_BAR);
|
||||
pkt.setComponent(ComponentSerializers.JSON.serialize(component));
|
||||
connection.write(pkt);
|
||||
return;
|
||||
} else {
|
||||
// Due to issues with action bar packets, we'll need to convert the text message into a legacy message
|
||||
// and then inject the legacy text into a component... yuck!
|
||||
JsonObject object = new JsonObject();
|
||||
object.addProperty("text", ComponentSerializers.LEGACY.serialize(component));
|
||||
json = VelocityServer.GSON.toJson(object);
|
||||
}
|
||||
} else {
|
||||
json = ComponentSerializers.JSON.serialize(component);
|
||||
}
|
||||
@@ -159,8 +169,8 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public ConnectionRequestBuilder createConnectionRequest(@NonNull ServerInfo info) {
|
||||
return new ConnectionRequestBuilderImpl(info);
|
||||
public ConnectionRequestBuilder createConnectionRequest(@NonNull RegisteredServer server) {
|
||||
return new ConnectionRequestBuilderImpl(server);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -191,56 +201,103 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
connection.closeWith(Disconnect.create(reason));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendTitle(Title title) {
|
||||
Preconditions.checkNotNull(title, "title");
|
||||
|
||||
if (title.equals(Titles.reset())) {
|
||||
connection.write(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
|
||||
} else if (title.equals(Titles.hide())) {
|
||||
connection.write(TitlePacket.hideForProtocolVersion(connection.getProtocolVersion()));
|
||||
} else if (title instanceof TextTitle) {
|
||||
TextTitle tt = (TextTitle) title;
|
||||
|
||||
if (tt.isResetBeforeSend()) {
|
||||
connection.delayedWrite(TitlePacket.resetForProtocolVersion(connection.getProtocolVersion()));
|
||||
}
|
||||
|
||||
if (tt.getTitle().isPresent()) {
|
||||
TitlePacket titlePkt = new TitlePacket();
|
||||
titlePkt.setAction(TitlePacket.SET_TITLE);
|
||||
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getTitle().get()));
|
||||
connection.delayedWrite(titlePkt);
|
||||
}
|
||||
if (tt.getSubtitle().isPresent()) {
|
||||
TitlePacket titlePkt = new TitlePacket();
|
||||
titlePkt.setAction(TitlePacket.SET_SUBTITLE);
|
||||
titlePkt.setComponent(ComponentSerializers.JSON.serialize(tt.getSubtitle().get()));
|
||||
connection.delayedWrite(titlePkt);
|
||||
}
|
||||
|
||||
if (tt.areTimesSet()) {
|
||||
TitlePacket timesPkt = TitlePacket.timesForProtocolVersion(connection.getProtocolVersion());
|
||||
timesPkt.setFadeIn(tt.getFadeIn());
|
||||
timesPkt.setStay(tt.getStay());
|
||||
timesPkt.setFadeOut(tt.getFadeOut());
|
||||
connection.delayedWrite(timesPkt);
|
||||
}
|
||||
connection.flush();
|
||||
} else {
|
||||
throw new IllegalArgumentException("Unknown title class " + title.getClass().getName());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public VelocityServerConnection getConnectedServer() {
|
||||
return connectedServer;
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Throwable throwable) {
|
||||
public void handleConnectionException(RegisteredServer server, Throwable throwable) {
|
||||
String error = ThrowableUtils.briefDescription(throwable);
|
||||
String userMessage;
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
userMessage = "Exception in server " + info.getName();
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||
userMessage = "Exception in server " + server.getServerInfo().getName();
|
||||
} else {
|
||||
logger.error("{}: unable to connect to server {}", this, info.getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + info.getName();
|
||||
logger.error("{}: unable to connect to server {}", this, server.getServerInfo().getName(), throwable);
|
||||
userMessage = "Exception connecting to server " + server.getServerInfo().getName();
|
||||
}
|
||||
handleConnectionException(info, null, TextComponent.builder()
|
||||
handleConnectionException(server, null, TextComponent.builder()
|
||||
.content(userMessage + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(TextComponent.of(error, TextColor.WHITE))
|
||||
.build());
|
||||
}
|
||||
|
||||
public void handleConnectionException(ServerInfo info, Disconnect disconnect) {
|
||||
public void handleConnectionException(RegisteredServer server, Disconnect disconnect) {
|
||||
Component disconnectReason = ComponentSerializers.JSON.deserialize(disconnect.getReason());
|
||||
String plainTextReason = PASS_THRU_TRANSLATE.serialize(disconnectReason);
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(info)) {
|
||||
logger.error("{}: kicked from server {}: {}", this, info.getName(), plainTextReason);
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(server.getServerInfo())) {
|
||||
logger.error("{}: kicked from server {}: {}", this, server.getServerInfo().getName(), plainTextReason);
|
||||
handleConnectionException(server, disconnectReason, TextComponent.builder()
|
||||
.content("Kicked from " + server.getServerInfo().getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
} else {
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this, info.getName(), plainTextReason);
|
||||
logger.error("{}: disconnected while connecting to {}: {}", this, server.getServerInfo().getName(), plainTextReason);
|
||||
handleConnectionException(server, disconnectReason, TextComponent.builder()
|
||||
.content("Unable to connect to " + server.getServerInfo().getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
}
|
||||
handleConnectionException(info, disconnectReason, TextComponent.builder()
|
||||
.content("Unable to connect to " + info.getName() + ": ")
|
||||
.color(TextColor.RED)
|
||||
.append(disconnectReason)
|
||||
.build());
|
||||
}
|
||||
|
||||
private void handleConnectionException(ServerInfo info, @Nullable Component kickReason, Component friendlyReason) {
|
||||
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(info);;
|
||||
private void handleConnectionException(RegisteredServer rs, @Nullable Component kickReason, Component friendlyReason) {
|
||||
boolean alreadyConnected = connectedServer != null && connectedServer.getServerInfo().equals(rs.getServerInfo());
|
||||
connectionInFlight = null;
|
||||
if (connectedServer == null) {
|
||||
// The player isn't yet connected to a server.
|
||||
Optional<ServerInfo> nextServer = getNextServerToTry();
|
||||
Optional<RegisteredServer> nextServer = getNextServerToTry();
|
||||
if (nextServer.isPresent()) {
|
||||
createConnectionRequest(nextServer.get()).fireAndForget();
|
||||
} else {
|
||||
connection.closeWith(Disconnect.create(friendlyReason));
|
||||
}
|
||||
} else if (connectedServer.getServerInfo().equals(info)) {
|
||||
} else if (connectedServer.getServerInfo().equals(rs.getServerInfo())) {
|
||||
// Already connected to the server being disconnected from.
|
||||
if (kickReason != null) {
|
||||
server.getEventManager().fire(new KickedFromServerEvent(this, info, kickReason, !alreadyConnected, friendlyReason))
|
||||
server.getEventManager().fire(new KickedFromServerEvent(this, rs, kickReason, !alreadyConnected, friendlyReason))
|
||||
.thenAcceptAsync(event -> {
|
||||
if (event.getResult() instanceof KickedFromServerEvent.DisconnectPlayer) {
|
||||
KickedFromServerEvent.DisconnectPlayer res = (KickedFromServerEvent.DisconnectPlayer) event.getResult();
|
||||
@@ -257,11 +314,11 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
connection.closeWith(Disconnect.create(friendlyReason));
|
||||
}
|
||||
} else {
|
||||
connection.write(Chat.create(friendlyReason));
|
||||
connection.write(Chat.createClientbound(friendlyReason));
|
||||
}
|
||||
}
|
||||
|
||||
Optional<ServerInfo> getNextServerToTry() {
|
||||
Optional<RegisteredServer> getNextServerToTry() {
|
||||
List<String> serversToTry = server.getConfiguration().getAttemptConnectionOrder();
|
||||
if (tryIndex >= serversToTry.size()) {
|
||||
return Optional.empty();
|
||||
@@ -272,21 +329,25 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
return server.getServers().getServer(toTryName);
|
||||
}
|
||||
|
||||
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
|
||||
private Optional<ConnectionRequestBuilder.Status> checkServer(RegisteredServer server) {
|
||||
Preconditions.checkState(server instanceof VelocityRegisteredServer, "Not a valid Velocity server.");
|
||||
if (connectionInFlight != null) {
|
||||
return CompletableFuture.completedFuture(
|
||||
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS)
|
||||
);
|
||||
return Optional.of(ConnectionRequestBuilder.Status.CONNECTION_IN_PROGRESS);
|
||||
}
|
||||
if (connectedServer != null && connectedServer.getServer().equals(server)) {
|
||||
return Optional.of(ConnectionRequestBuilder.Status.ALREADY_CONNECTED);
|
||||
}
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
if (connectedServer != null && connectedServer.getServerInfo().equals(request.getServer())) {
|
||||
return CompletableFuture.completedFuture(
|
||||
ConnectionRequestResults.plainResult(ConnectionRequestBuilder.Status.ALREADY_CONNECTED)
|
||||
);
|
||||
private CompletableFuture<ConnectionRequestBuilder.Result> connect(ConnectionRequestBuilderImpl request) {
|
||||
Optional<ConnectionRequestBuilder.Status> initialCheck = checkServer(request.getServer());
|
||||
if (initialCheck.isPresent()) {
|
||||
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(initialCheck.get()));
|
||||
}
|
||||
|
||||
// Otherwise, initiate the connection.
|
||||
ServerPreConnectEvent event = new ServerPreConnectEvent(this, ServerPreConnectEvent.ServerResult.allowed(request.getServer()));
|
||||
ServerPreConnectEvent event = new ServerPreConnectEvent(this, request.getServer());
|
||||
return server.getEventManager().fire(event)
|
||||
.thenCompose((newEvent) -> {
|
||||
if (!newEvent.getResult().isAllowed()) {
|
||||
@@ -295,7 +356,12 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
);
|
||||
}
|
||||
|
||||
return new VelocityServerConnection(newEvent.getResult().getInfo().get(), this, server).connect();
|
||||
RegisteredServer rs = newEvent.getResult().getServer().get();
|
||||
Optional<ConnectionRequestBuilder.Status> lastCheck = checkServer(rs);
|
||||
if (lastCheck.isPresent()) {
|
||||
return CompletableFuture.completedFuture(ConnectionRequestResults.plainResult(lastCheck.get()));
|
||||
}
|
||||
return new VelocityServerConnection((VelocityRegisteredServer) rs, this, server).connect();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -336,30 +402,37 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasPermission(String permission) {
|
||||
return permissionFunction.getPermissionSetting(permission).asBoolean();
|
||||
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
|
||||
return permissionFunction.getPermissionValue(permission);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
Preconditions.checkNotNull(identifier, "identifier");
|
||||
Preconditions.checkNotNull(data, "data");
|
||||
PluginMessage message = new PluginMessage();
|
||||
message.setChannel(identifier.getId());
|
||||
message.setData(data);
|
||||
connection.write(message);
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void spoofChatInput(String input) {
|
||||
Preconditions.checkArgument(input.length() <= Chat.MAX_SERVERBOUND_MESSAGE_LENGTH, "input cannot be greater than " + Chat.MAX_SERVERBOUND_MESSAGE_LENGTH + " characters in length");
|
||||
connectedServer.getMinecraftConnection().write(Chat.createServerbound(input));
|
||||
}
|
||||
|
||||
private class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder {
|
||||
private final ServerInfo info;
|
||||
private final RegisteredServer server;
|
||||
|
||||
ConnectionRequestBuilderImpl(ServerInfo info) {
|
||||
this.info = Preconditions.checkNotNull(info, "info");
|
||||
ConnectionRequestBuilderImpl(RegisteredServer server) {
|
||||
this.server = Preconditions.checkNotNull(server, "info");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServer() {
|
||||
return info;
|
||||
public RegisteredServer getServer() {
|
||||
return server;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -372,7 +445,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
connect()
|
||||
.whenCompleteAsync((status, throwable) -> {
|
||||
if (throwable != null) {
|
||||
handleConnectionException(info, throwable);
|
||||
handleConnectionException(server, throwable);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -387,7 +460,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
|
||||
// Ignored; the plugin probably already handled this.
|
||||
break;
|
||||
case SERVER_DISCONNECTED:
|
||||
handleConnectionException(info, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
|
||||
handleConnectionException(server, Disconnect.create(status.getReason().orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR)));
|
||||
break;
|
||||
}
|
||||
}, connection.getChannel().eventLoop());
|
||||
|
@@ -5,12 +5,12 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.event.connection.ConnectionHandshakeEvent;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
|
||||
public class InitialConnectSessionHandler implements MinecraftSessionHandler {
|
||||
private final ConnectedPlayer player;
|
||||
|
@@ -2,22 +2,23 @@ package com.velocitypowered.proxy.connection.client;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.event.connection.LoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PostLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.util.GameProfile;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.PlayerInfoForwarding;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.connection.VelocityConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.*;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.util.EncryptionUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
@@ -39,6 +40,7 @@ import java.util.Optional;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
|
||||
public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
private static final Logger logger = LogManager.getLogger(LoginSessionHandler.class);
|
||||
private static final String MOJANG_SERVER_AUTH_URL =
|
||||
"https://sessionserver.mojang.com/session/minecraft/hasJoined?username=%s&serverId=%s&ip=%s";
|
||||
@@ -157,7 +159,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
return;
|
||||
}
|
||||
|
||||
if (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed()) {
|
||||
if (!result.isForceOfflineMode() && (server.getConfiguration().isOnlineMode() || result.isOnlineModeAllowed())) {
|
||||
// Request encryption.
|
||||
EncryptionRequest request = generateRequest();
|
||||
this.verify = Arrays.copyOf(request.getVerifyToken(), 4);
|
||||
@@ -218,7 +220,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
}
|
||||
|
||||
private void handleProxyLogin(ConnectedPlayer player) {
|
||||
Optional<ServerInfo> toTry = player.getNextServerToTry();
|
||||
Optional<RegisteredServer> toTry = player.getNextServerToTry();
|
||||
if (!toTry.isPresent()) {
|
||||
player.close(TextComponent.of("No available servers", TextColor.RED));
|
||||
return;
|
||||
@@ -244,7 +246,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
|
||||
|
||||
logger.info("{} has connected", player);
|
||||
inbound.setSessionHandler(new InitialConnectSessionHandler(player));
|
||||
player.createConnectionRequest(toTry.get()).fireAndForget();
|
||||
server.getEventManager().fire(new PostLoginEvent(player)).thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@@ -4,16 +4,16 @@ import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.event.proxy.ProxyPingEvent;
|
||||
import com.velocitypowered.api.proxy.InboundConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.config.VelocityConfiguration;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusPing;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
|
||||
@@ -49,7 +49,7 @@ public class StatusSessionHandler implements MinecraftSessionHandler {
|
||||
new ServerPing.Players(server.getPlayerCount(), configuration.getShowMaxPlayers(), ImmutableList.of()),
|
||||
configuration.getMotdComponent(),
|
||||
configuration.getFavicon(),
|
||||
configuration.isAnnounceForge() ? ServerPing.Modinfo.DEFAULT : null
|
||||
configuration.isAnnounceForge() ? ServerPing.ModInfo.DEFAULT : null
|
||||
);
|
||||
|
||||
ProxyPingEvent event = new ProxyPingEvent(inboundWrapper, initialPing);
|
||||
|
@@ -7,6 +7,7 @@ public class ConnectionMessages {
|
||||
public static final TextComponent ALREADY_CONNECTED = TextComponent.of("You are already connected to this server!", TextColor.RED);
|
||||
public static final TextComponent IN_PROGRESS = TextComponent.of("You are already connecting to a server!", TextColor.RED);
|
||||
public static final TextComponent INTERNAL_SERVER_CONNECTION_ERROR = TextComponent.of("Internal server connection error");
|
||||
public static final TextComponent UNEXPECTED_DISCONNECT = TextComponent.of("Unexpectedly disconnected from server - crash?");
|
||||
|
||||
private ConnectionMessages() {
|
||||
throw new AssertionError();
|
||||
|
@@ -1,28 +1,57 @@
|
||||
package com.velocitypowered.proxy.console;
|
||||
|
||||
import com.velocitypowered.api.command.CommandSource;
|
||||
import com.velocitypowered.api.event.permission.PermissionsSetupEvent;
|
||||
import com.velocitypowered.api.permission.PermissionFunction;
|
||||
import com.velocitypowered.api.permission.Tristate;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.TextComponent;
|
||||
import net.kyori.text.format.TextColor;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import net.minecrell.terminalconsole.SimpleTerminalConsole;
|
||||
import org.jline.reader.*;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
import org.jline.reader.Candidate;
|
||||
import org.jline.reader.LineReader;
|
||||
import org.jline.reader.LineReaderBuilder;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
public final class VelocityConsole extends SimpleTerminalConsole {
|
||||
public final class VelocityConsole extends SimpleTerminalConsole implements CommandSource {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityConsole.class);
|
||||
|
||||
private final VelocityServer server;
|
||||
private PermissionFunction permissionFunction = PermissionFunction.ALWAYS_TRUE;
|
||||
|
||||
public VelocityConsole(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sendMessage(Component component) {
|
||||
logger.info(ComponentSerializers.LEGACY.serialize(component));
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NonNull Tristate getPermissionValue(@NonNull String permission) {
|
||||
return this.permissionFunction.getPermissionValue(permission);
|
||||
}
|
||||
|
||||
public void setupPermissions() {
|
||||
PermissionsSetupEvent event = new PermissionsSetupEvent(this, s -> PermissionFunction.ALWAYS_TRUE);
|
||||
this.server.getEventManager().fire(event).join(); // this is called on startup, we can safely #join
|
||||
this.permissionFunction = event.createFunction(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected LineReader buildReader(LineReaderBuilder builder) {
|
||||
return super.buildReader(builder
|
||||
.appName("Velocity")
|
||||
.completer((reader, parsedLine, list) -> {
|
||||
Optional<List<String>> o = server.getCommandManager().offerSuggestions(server.getConsoleCommandSource(), parsedLine.line());
|
||||
Optional<List<String>> o = this.server.getCommandManager().offerSuggestions(this, parsedLine.line());
|
||||
o.ifPresent(offers -> {
|
||||
for (String offer : offers) {
|
||||
list.add(new Candidate(offer));
|
||||
@@ -39,8 +68,8 @@ public final class VelocityConsole extends SimpleTerminalConsole {
|
||||
|
||||
@Override
|
||||
protected void runCommand(String command) {
|
||||
if (!this.server.getCommandManager().execute(this.server.getConsoleCommandSource(), command)) {
|
||||
server.getConsoleCommandSource().sendMessage(TextComponent.of("Command not found.", TextColor.RED));
|
||||
if (!this.server.getCommandManager().execute(this, command)) {
|
||||
sendMessage(TextComponent.of("Command not found.", TextColor.RED));
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -2,10 +2,10 @@ package com.velocitypowered.proxy.messages;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.messages.*;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
|
||||
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
@@ -13,39 +13,20 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityChannelRegistrar.class);
|
||||
private final Map<String, MessageHandler> handlers = new ConcurrentHashMap<>();
|
||||
private final Map<String, ChannelIdentifier> identifierMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void register(MessageHandler handler, ChannelIdentifier... identifiers) {
|
||||
public void register(ChannelIdentifier... identifiers) {
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
Preconditions.checkArgument(identifier instanceof LegacyChannelIdentifier || identifier instanceof MinecraftChannelIdentifier,
|
||||
"identifier is unknown");
|
||||
}
|
||||
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
handlers.put(identifier.getId(), handler);
|
||||
identifierMap.put(identifier.getId(), identifier);
|
||||
}
|
||||
}
|
||||
|
||||
public MessageHandler.ForwardStatus handlePluginMessage(ChannelMessageSource source, ChannelSide side, PluginMessage message) {
|
||||
MessageHandler handler = handlers.get(message.getChannel());
|
||||
ChannelIdentifier identifier = identifierMap.get(message.getChannel());
|
||||
if (handler == null || identifier == null) {
|
||||
return MessageHandler.ForwardStatus.FORWARD;
|
||||
}
|
||||
|
||||
try {
|
||||
return handler.handle(source, side, identifier, message.getData());
|
||||
} catch (Exception e) {
|
||||
logger.info("Unable to handle plugin message on channel {} for {}", message.getChannel(), source);
|
||||
// In case of doubt, do not forward the message on.
|
||||
return MessageHandler.ForwardStatus.HANDLED;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void unregister(ChannelIdentifier... identifiers) {
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
@@ -54,7 +35,6 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
}
|
||||
|
||||
for (ChannelIdentifier identifier : identifiers) {
|
||||
handlers.remove(identifier.getId());
|
||||
identifierMap.remove(identifier.getId());
|
||||
}
|
||||
}
|
||||
@@ -73,4 +53,8 @@ public class VelocityChannelRegistrar implements ChannelRegistrar {
|
||||
public boolean registered(String id) {
|
||||
return identifierMap.containsKey(id);
|
||||
}
|
||||
|
||||
public ChannelIdentifier getFromId(String id) {
|
||||
return identifierMap.get(id);
|
||||
}
|
||||
}
|
||||
|
@@ -7,21 +7,11 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.HandshakeSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.GS4QueryHandler;
|
||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.LegacyPingEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.*;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.bootstrap.ServerBootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.epoll.Epoll;
|
||||
import io.netty.channel.epoll.EpollDatagramChannel;
|
||||
import io.netty.channel.epoll.EpollEventLoopGroup;
|
||||
import io.netty.channel.epoll.EpollServerSocketChannel;
|
||||
import io.netty.channel.epoll.EpollSocketChannel;
|
||||
import io.netty.channel.epoll.*;
|
||||
import io.netty.channel.kqueue.*;
|
||||
import io.netty.channel.nio.NioEventLoopGroup;
|
||||
import io.netty.channel.socket.DatagramChannel;
|
||||
@@ -75,7 +65,7 @@ public final class ConnectionManager {
|
||||
@Override
|
||||
protected void initChannel(final Channel ch) {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(CLIENT_READ_TIMEOUT_SECONDS, TimeUnit.SECONDS))
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(LEGACY_PING_DECODER, new LegacyPingDecoder())
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(LEGACY_PING_ENCODER, LegacyPingEncoder.INSTANCE)
|
||||
@@ -125,7 +115,9 @@ public final class ConnectionManager {
|
||||
public Bootstrap createWorker() {
|
||||
return new Bootstrap()
|
||||
.channel(this.transportType.socketChannelClass)
|
||||
.group(this.workerGroup);
|
||||
.group(this.workerGroup)
|
||||
.option(ChannelOption.TCP_NODELAY, true)
|
||||
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, server.getConfiguration().getConnectTimeout());
|
||||
}
|
||||
|
||||
public void shutdown() {
|
||||
|
@@ -13,7 +13,4 @@ public interface Connections {
|
||||
String MINECRAFT_DECODER = "minecraft-decoder";
|
||||
String MINECRAFT_ENCODER = "minecraft-encoder";
|
||||
String READ_TIMEOUT = "read-timeout";
|
||||
|
||||
int CLIENT_READ_TIMEOUT_SECONDS = 30; // client -> proxy
|
||||
int SERVER_READ_TIMEOUT_SECONDS = 30; // proxy -> server
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ package com.velocitypowered.proxy.network.http;
|
||||
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import io.netty.bootstrap.Bootstrap;
|
||||
import io.netty.channel.*;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.pool.*;
|
||||
import io.netty.handler.codec.http.*;
|
||||
import io.netty.handler.ssl.SslContext;
|
||||
|
@@ -13,7 +13,10 @@ import com.velocitypowered.proxy.util.concurrency.RecordingThreadFactory;
|
||||
import net.kyori.event.EventSubscriber;
|
||||
import net.kyori.event.PostResult;
|
||||
import net.kyori.event.SimpleEventBus;
|
||||
import net.kyori.event.method.*;
|
||||
import net.kyori.event.method.EventExecutor;
|
||||
import net.kyori.event.method.MethodScanner;
|
||||
import net.kyori.event.method.MethodSubscriptionAdapter;
|
||||
import net.kyori.event.method.SimpleMethodSubscriptionAdapter;
|
||||
import net.kyori.event.method.asm.ASMEventExecutorFactory;
|
||||
import org.apache.logging.log4j.LogManager;
|
||||
import org.apache.logging.log4j.Logger;
|
||||
@@ -21,8 +24,13 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.net.URL;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
public class VelocityEventManager implements EventManager {
|
||||
private static final Logger logger = LogManager.getLogger(VelocityEventManager.class);
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.plugin;
|
||||
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
|
@@ -2,7 +2,9 @@ package com.velocitypowered.proxy.plugin.loader;
|
||||
|
||||
import com.google.inject.Guice;
|
||||
import com.google.inject.Injector;
|
||||
import com.velocitypowered.api.plugin.*;
|
||||
import com.velocitypowered.api.plugin.InvalidPluginException;
|
||||
import com.velocitypowered.api.plugin.PluginContainer;
|
||||
import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.meta.PluginDependency;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
|
@@ -8,7 +8,6 @@ import com.velocitypowered.api.plugin.PluginDescription;
|
||||
import com.velocitypowered.api.plugin.PluginManager;
|
||||
import com.velocitypowered.api.plugin.annotation.DataDirectory;
|
||||
import com.velocitypowered.api.proxy.ProxyServer;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
|
@@ -8,7 +8,7 @@ import io.netty.util.collection.IntObjectMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntMap;
|
||||
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
|
||||
@@ -23,7 +23,7 @@ public enum StateRegistry {
|
||||
},
|
||||
STATUS {
|
||||
{
|
||||
SERVERBOUND.register(StatusRequest.class, StatusRequest::new,
|
||||
SERVERBOUND.register(StatusRequest.class, () -> StatusRequest.INSTANCE,
|
||||
genericMappings(0x00));
|
||||
SERVERBOUND.register(StatusPing.class, StatusPing::new,
|
||||
genericMappings(0x01));
|
||||
@@ -40,8 +40,7 @@ public enum StateRegistry {
|
||||
map(0x14, MINECRAFT_1_8, false),
|
||||
map(0x01, MINECRAFT_1_9, false),
|
||||
map(0x02, MINECRAFT_1_12, false),
|
||||
map(0x01, MINECRAFT_1_12_1, false),
|
||||
map(0x05, MINECRAFT_1_13, false));
|
||||
map(0x01, MINECRAFT_1_12_1, false));
|
||||
SERVERBOUND.register(Chat.class, Chat::new,
|
||||
map(0x01, MINECRAFT_1_8, false),
|
||||
map(0x02, MINECRAFT_1_9, false),
|
||||
@@ -77,10 +76,9 @@ public enum StateRegistry {
|
||||
map(0x0F, MINECRAFT_1_12, true),
|
||||
map(0x0E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TabCompleteResponse.class, TabCompleteResponse::new,
|
||||
map(0x3A, MINECRAFT_1_8, true),
|
||||
map(0x0E, MINECRAFT_1_9, true),
|
||||
map(0x0E, MINECRAFT_1_12, true),
|
||||
map(0x10, MINECRAFT_1_13, true));
|
||||
map(0x3A, MINECRAFT_1_8, false),
|
||||
map(0x0E, MINECRAFT_1_9, false),
|
||||
map(0x0E, MINECRAFT_1_12, false));
|
||||
CLIENTBOUND.register(PluginMessage.class, PluginMessage::new,
|
||||
map(0x3F, MINECRAFT_1_8, false),
|
||||
map(0x18, MINECRAFT_1_9, false),
|
||||
@@ -114,30 +112,12 @@ public enum StateRegistry {
|
||||
map(0x49, MINECRAFT_1_12, true),
|
||||
map(0x4A, MINECRAFT_1_12_1, true),
|
||||
map(0x4E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(ScoreboardDisplay.class, ScoreboardDisplay::new,
|
||||
map(0x3D, MINECRAFT_1_8, true),
|
||||
map(0x38, MINECRAFT_1_9, true),
|
||||
map(0x3A, MINECRAFT_1_12, true),
|
||||
map(0x3B, MINECRAFT_1_12_1, true),
|
||||
map(0x3E, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(ScoreboardObjective.class, ScoreboardObjective::new,
|
||||
map(0x3B, MINECRAFT_1_8, true),
|
||||
map(0x3F, MINECRAFT_1_9, true),
|
||||
map(0x41, MINECRAFT_1_12, true),
|
||||
map(0x42, MINECRAFT_1_12_1, true),
|
||||
map(0x45, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(ScoreboardTeam.class, ScoreboardTeam::new,
|
||||
map(0x3E, MINECRAFT_1_8, true),
|
||||
map(0x41, MINECRAFT_1_9, true),
|
||||
map(0x43, MINECRAFT_1_12, true),
|
||||
map(0x44, MINECRAFT_1_12_1, true),
|
||||
map(0x47, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(ScoreboardSetScore.class, ScoreboardSetScore::new,
|
||||
map(0x3C, MINECRAFT_1_8, true),
|
||||
map(0x42, MINECRAFT_1_9, true),
|
||||
map(0x44, MINECRAFT_1_12, true),
|
||||
map(0x45, MINECRAFT_1_12_1, true),
|
||||
map(0x48, MINECRAFT_1_13, true));
|
||||
CLIENTBOUND.register(TitlePacket.class, TitlePacket::new,
|
||||
map(0x45, MINECRAFT_1_8, true),
|
||||
map(0x45, MINECRAFT_1_9, true),
|
||||
map(0x47, MINECRAFT_1_12, true),
|
||||
map(0x48, MINECRAFT_1_12_1, true),
|
||||
map(0x4B, MINECRAFT_1_13, true));
|
||||
}
|
||||
},
|
||||
LOGIN {
|
||||
|
@@ -101,9 +101,6 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
queryResponse.writeByte(QUERY_TYPE_STAT);
|
||||
queryResponse.writeInt(sessionId);
|
||||
|
||||
// Fetch information
|
||||
Collection<Player> players = server.getAllPlayers();
|
||||
|
||||
// Start writing the response
|
||||
ResponseWriter responseWriter = new ResponseWriter(queryResponse, queryMessage.readableBytes() == 0);
|
||||
responseWriter.write("hostname", ComponentSerializers.PLAIN.serialize(server.getConfiguration().getMotdComponent()));
|
||||
@@ -114,12 +111,14 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
responseWriter.write("plugins", "");
|
||||
|
||||
responseWriter.write("map", "Velocity");
|
||||
responseWriter.write("numplayers", players.size());
|
||||
responseWriter.write("numplayers", server.getPlayerCount());
|
||||
responseWriter.write("maxplayers", server.getConfiguration().getShowMaxPlayers());
|
||||
responseWriter.write("hostport", server.getConfiguration().getBind().getPort());
|
||||
responseWriter.write("hostip", server.getConfiguration().getBind().getHostString());
|
||||
|
||||
responseWriter.writePlayers(players);
|
||||
if (!responseWriter.isBasic) {
|
||||
responseWriter.writePlayers(server.getAllPlayers());
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -132,6 +131,8 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
|
||||
ctx.writeAndFlush(responsePacket);
|
||||
} catch (Exception e) {
|
||||
logger.warn("Error while trying to handle a query packet from {}", msg.sender(), e);
|
||||
// NB: Only need to explicitly release upon exception, writing the response out will decrement the reference
|
||||
// count.
|
||||
responsePacket.release();
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToMessageDecoder;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.natives.compression.VelocityCompressor;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
|
@@ -1,7 +1,10 @@
|
||||
package com.velocitypowered.proxy.protocol.netty;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.*;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.CorruptedFrameException;
|
||||
|
@@ -4,7 +4,6 @@ import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.channel.ChannelHandler;
|
||||
import io.netty.channel.ChannelHandlerContext;
|
||||
import io.netty.handler.codec.MessageToByteEncoder;
|
||||
import io.netty.handler.codec.MessageToMessageEncoder;
|
||||
|
||||
import java.util.List;
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
@@ -10,6 +10,8 @@ import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
public class Chat implements MinecraftPacket {
|
||||
public static final byte CHAT = (byte) 0;
|
||||
public static final int MAX_SERVERBOUND_MESSAGE_LENGTH = 256;
|
||||
|
||||
private String message;
|
||||
private byte type;
|
||||
|
||||
@@ -61,12 +63,16 @@ public class Chat implements MinecraftPacket {
|
||||
}
|
||||
}
|
||||
|
||||
public static Chat create(Component component) {
|
||||
return create(component, CHAT);
|
||||
public static Chat createClientbound(Component component) {
|
||||
return createClientbound(component, CHAT);
|
||||
}
|
||||
|
||||
public static Chat create(Component component, byte type) {
|
||||
public static Chat createClientbound(Component component, byte type) {
|
||||
Preconditions.checkNotNull(component, "component");
|
||||
return new Chat(ComponentSerializers.JSON.serialize(component), type);
|
||||
}
|
||||
|
||||
public static Chat createServerbound(String message) {
|
||||
return new Chat(message, CHAT);
|
||||
}
|
||||
}
|
||||
|
@@ -1,8 +1,8 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@@ -2,13 +2,13 @@ package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants.Direction;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializer;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolUtils.writeString;
|
||||
|
||||
public class HeaderAndFooter implements MinecraftPacket {
|
||||
|
||||
private static final HeaderAndFooter RESET = new HeaderAndFooter("{\"translate\":\"\"}", "{\"translate\":\"\"}");
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@@ -1,47 +0,0 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ScoreboardDisplay implements MinecraftPacket {
|
||||
private byte position;
|
||||
private String displayName;
|
||||
|
||||
public byte getPosition() {
|
||||
return position;
|
||||
}
|
||||
|
||||
public void setPosition(byte position) {
|
||||
this.position = position;
|
||||
}
|
||||
|
||||
public String getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(String displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardDisplay{" +
|
||||
"position=" + position +
|
||||
", displayName='" + displayName + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.position = buf.readByte();
|
||||
this.displayName = ProtocolUtils.readString(buf, 16);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
buf.writeByte(position);
|
||||
ProtocolUtils.writeString(buf, displayName);
|
||||
}
|
||||
}
|
@@ -1,93 +0,0 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.util.ScoreboardProtocolUtil;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
||||
public class ScoreboardObjective implements MinecraftPacket {
|
||||
public static final byte ADD = (byte) 0;
|
||||
public static final byte REMOVE = (byte) 1;
|
||||
public static final byte CHANGE = (byte) 2;
|
||||
private String id;
|
||||
private byte mode;
|
||||
private Component displayName;
|
||||
private ObjectiveMode type;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(byte mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public ObjectiveMode getType() {
|
||||
return type;
|
||||
}
|
||||
|
||||
public void setType(ObjectiveMode type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardObjective{" +
|
||||
"id='" + id + '\'' +
|
||||
", mode=" + mode +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", type='" + type + '\'' +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readString(buf, 16);
|
||||
this.mode = buf.readByte();
|
||||
if (this.mode != REMOVE) {
|
||||
this.displayName = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
this.type = ScoreboardProtocolUtil.getMode(ProtocolUtils.readVarInt(buf));
|
||||
} else {
|
||||
this.type = ScoreboardProtocolUtil.getMode(ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, id);
|
||||
buf.writeByte(mode);
|
||||
if (this.mode != REMOVE) {
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, displayName);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
ProtocolUtils.writeVarInt(buf, type.ordinal());
|
||||
} else {
|
||||
ProtocolUtils.writeString(buf, type.name().toLowerCase());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum ObjectiveMode {
|
||||
INTEGER,
|
||||
HEARTS
|
||||
}
|
||||
}
|
@@ -1,77 +0,0 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class ScoreboardSetScore implements MinecraftPacket {
|
||||
public static final byte CHANGE = (byte) 0;
|
||||
public static final byte REMOVE = (byte) 1;
|
||||
private String entity;
|
||||
private byte action;
|
||||
private String objective;
|
||||
private int value;
|
||||
|
||||
public String getEntity() {
|
||||
return entity;
|
||||
}
|
||||
|
||||
public void setEntity(String entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
public byte getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(byte action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getObjective() {
|
||||
return objective;
|
||||
}
|
||||
|
||||
public void setObjective(String objective) {
|
||||
this.objective = objective;
|
||||
}
|
||||
|
||||
public int getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
public void setValue(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardSetScore{" +
|
||||
"entity='" + entity + '\'' +
|
||||
", action=" + action +
|
||||
", objective='" + objective + '\'' +
|
||||
", value=" + value +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.entity = ProtocolUtils.readString(buf, 40);
|
||||
this.action = buf.readByte();
|
||||
this.objective = ProtocolUtils.readString(buf, 16);
|
||||
if (this.action != REMOVE) {
|
||||
value = ProtocolUtils.readVarInt(buf);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, entity);
|
||||
buf.writeByte(action);
|
||||
ProtocolUtils.writeString(buf, objective);
|
||||
if (this.action != REMOVE) {
|
||||
ProtocolUtils.writeVarInt(buf, value);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,215 +0,0 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class ScoreboardTeam implements MinecraftPacket {
|
||||
public static final byte ADD = (byte) 0;
|
||||
public static final byte REMOVE = (byte) 1;
|
||||
public static final byte UPDATE = (byte) 2;
|
||||
public static final byte ADD_PLAYER = (byte) 3;
|
||||
public static final byte REMOVE_PLAYER = (byte) 4;
|
||||
private String id;
|
||||
private byte mode;
|
||||
|
||||
private Component displayName;
|
||||
private Component prefix;
|
||||
private Component suffix;
|
||||
private byte flags;
|
||||
private String nameTagVisibility;
|
||||
private String collisionRule;
|
||||
private int color;
|
||||
private List<String> entities;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(String id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public byte getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public void setMode(byte mode) {
|
||||
this.mode = mode;
|
||||
}
|
||||
|
||||
public Component getDisplayName() {
|
||||
return displayName;
|
||||
}
|
||||
|
||||
public void setDisplayName(Component displayName) {
|
||||
this.displayName = displayName;
|
||||
}
|
||||
|
||||
public Component getPrefix() {
|
||||
return prefix;
|
||||
}
|
||||
|
||||
public void setPrefix(Component prefix) {
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
public Component getSuffix() {
|
||||
return suffix;
|
||||
}
|
||||
|
||||
public void setSuffix(Component suffix) {
|
||||
this.suffix = suffix;
|
||||
}
|
||||
|
||||
public byte getFlags() {
|
||||
return flags;
|
||||
}
|
||||
|
||||
public void setFlags(byte flags) {
|
||||
this.flags = flags;
|
||||
}
|
||||
|
||||
public String getNameTagVisibility() {
|
||||
return nameTagVisibility;
|
||||
}
|
||||
|
||||
public void setNameTagVisibility(String nameTagVisibility) {
|
||||
this.nameTagVisibility = nameTagVisibility;
|
||||
}
|
||||
|
||||
public String getCollisionRule() {
|
||||
return collisionRule;
|
||||
}
|
||||
|
||||
public void setCollisionRule(String collisionRule) {
|
||||
this.collisionRule = collisionRule;
|
||||
}
|
||||
|
||||
public int getColor() {
|
||||
return color;
|
||||
}
|
||||
|
||||
public void setColor(int color) {
|
||||
this.color = color;
|
||||
}
|
||||
|
||||
public List<String> getEntities() {
|
||||
return entities;
|
||||
}
|
||||
|
||||
public void setEntities(List<String> entities) {
|
||||
this.entities = entities;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "ScoreboardTeam{" +
|
||||
"id='" + id + '\'' +
|
||||
", mode=" + mode +
|
||||
", displayName='" + displayName + '\'' +
|
||||
", prefix='" + prefix + '\'' +
|
||||
", suffix='" + suffix + '\'' +
|
||||
", flags=" + flags +
|
||||
", nameTagVisibility='" + nameTagVisibility + '\'' +
|
||||
", collisionRule='" + collisionRule + '\'' +
|
||||
", color=" + color +
|
||||
", entities=" + entities +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
this.id = ProtocolUtils.readString(buf, 16);
|
||||
this.mode = buf.readByte();
|
||||
|
||||
switch (mode) {
|
||||
case ADD:
|
||||
case UPDATE:
|
||||
this.displayName = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
this.prefix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
this.suffix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
}
|
||||
this.flags = buf.readByte();
|
||||
this.nameTagVisibility = ProtocolUtils.readString(buf, 32);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
|
||||
this.collisionRule = ProtocolUtils.readString(buf, 32);
|
||||
}
|
||||
this.color = protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2 ? buf.readByte() :
|
||||
ProtocolUtils.readVarInt(buf);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
this.prefix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
this.suffix = ProtocolUtils.readScoreboardTextComponent(buf, protocolVersion);
|
||||
}
|
||||
if (mode == ADD) {
|
||||
this.entities = readEntities(buf);
|
||||
}
|
||||
break;
|
||||
case REMOVE: // remove
|
||||
break;
|
||||
case ADD_PLAYER: // add player
|
||||
case REMOVE_PLAYER: // remove player
|
||||
this.entities = readEntities(buf);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeString(buf, id);
|
||||
buf.writeByte(mode);
|
||||
switch (mode) {
|
||||
case ADD:
|
||||
case UPDATE:
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, displayName);
|
||||
if (protocolVersion <= ProtocolConstants.MINECRAFT_1_12_2) {
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix);
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, suffix);
|
||||
}
|
||||
buf.writeByte(flags);
|
||||
ProtocolUtils.writeString(buf, nameTagVisibility);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_9) {
|
||||
ProtocolUtils.writeString(buf, collisionRule);
|
||||
}
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_13) {
|
||||
ProtocolUtils.writeVarInt(buf, color);
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, prefix);
|
||||
ProtocolUtils.writeScoreboardTextComponent(buf, protocolVersion, suffix);
|
||||
} else {
|
||||
buf.writeByte(color);
|
||||
}
|
||||
if (mode == ADD) {
|
||||
writeEntities(buf, entities);
|
||||
}
|
||||
break;
|
||||
case REMOVE:
|
||||
break;
|
||||
case ADD_PLAYER:
|
||||
case REMOVE_PLAYER:
|
||||
writeEntities(buf, entities);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private static List<String> readEntities(ByteBuf buf) {
|
||||
List<String> entities = new ArrayList<>();
|
||||
int size = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < size; i++) {
|
||||
entities.add(ProtocolUtils.readString(buf, 40));
|
||||
}
|
||||
return entities;
|
||||
}
|
||||
|
||||
private static void writeEntities(ByteBuf buf, List<String> entities) {
|
||||
ProtocolUtils.writeVarInt(buf, entities.size());
|
||||
for (String entity : entities) {
|
||||
ProtocolUtils.writeString(buf, entity);
|
||||
}
|
||||
}
|
||||
}
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@@ -1,10 +1,16 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class StatusRequest implements MinecraftPacket {
|
||||
public static final StatusRequest INSTANCE = new StatusRequest();
|
||||
|
||||
private StatusRequest() {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
|
||||
@@ -14,4 +20,9 @@ public class StatusRequest implements MinecraftPacket {
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "StatusRequest";
|
||||
}
|
||||
}
|
||||
|
@@ -1,7 +1,7 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
|
@@ -9,20 +9,11 @@ import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_1
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_9;
|
||||
|
||||
public class TabCompleteRequest implements MinecraftPacket {
|
||||
private int transactionId;
|
||||
private String command;
|
||||
private boolean assumeCommand;
|
||||
private boolean hasPosition;
|
||||
private long position;
|
||||
|
||||
public int getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public void setTransactionId(int transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
public String getCommand() {
|
||||
return command;
|
||||
}
|
||||
@@ -58,8 +49,7 @@ public class TabCompleteRequest implements MinecraftPacket {
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabCompleteRequest{" +
|
||||
"transactionId=" + transactionId +
|
||||
", command='" + command + '\'' +
|
||||
"command='" + command + '\'' +
|
||||
", assumeCommand=" + assumeCommand +
|
||||
", hasPosition=" + hasPosition +
|
||||
", position=" + position +
|
||||
@@ -68,35 +58,25 @@ public class TabCompleteRequest implements MinecraftPacket {
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_13) {
|
||||
this.transactionId = ProtocolUtils.readVarInt(buf);
|
||||
this.command = ProtocolUtils.readString(buf);
|
||||
} else {
|
||||
this.command = ProtocolUtils.readString(buf);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
this.assumeCommand = buf.readBoolean();
|
||||
}
|
||||
this.hasPosition = buf.readBoolean();
|
||||
if (hasPosition) {
|
||||
this.position = buf.readLong();
|
||||
}
|
||||
this.command = ProtocolUtils.readString(buf);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
this.assumeCommand = buf.readBoolean();
|
||||
}
|
||||
this.hasPosition = buf.readBoolean();
|
||||
if (hasPosition) {
|
||||
this.position = buf.readLong();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_13) {
|
||||
ProtocolUtils.writeVarInt(buf, transactionId);
|
||||
ProtocolUtils.writeString(buf, command);
|
||||
} else {
|
||||
ProtocolUtils.writeString(buf, command);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
buf.writeBoolean(assumeCommand);
|
||||
}
|
||||
buf.writeBoolean(hasPosition);
|
||||
if (hasPosition) {
|
||||
buf.writeLong(position);
|
||||
}
|
||||
ProtocolUtils.writeString(buf, command);
|
||||
if (protocolVersion >= MINECRAFT_1_9) {
|
||||
buf.writeBoolean(assumeCommand);
|
||||
}
|
||||
buf.writeBoolean(hasPosition);
|
||||
if (hasPosition) {
|
||||
buf.writeLong(position);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -4,125 +4,37 @@ import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import net.kyori.text.Component;
|
||||
import net.kyori.text.serializer.ComponentSerializers;
|
||||
import org.checkerframework.checker.nullness.qual.Nullable;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_13;
|
||||
|
||||
public class TabCompleteResponse implements MinecraftPacket {
|
||||
private int transactionId;
|
||||
private int start;
|
||||
private int length;
|
||||
private final List<Offer> offers = new ArrayList<>();
|
||||
private final List<String> offers = new ArrayList<>();
|
||||
|
||||
public int getTransactionId() {
|
||||
return transactionId;
|
||||
}
|
||||
|
||||
public void setTransactionId(int transactionId) {
|
||||
this.transactionId = transactionId;
|
||||
}
|
||||
|
||||
public int getStart() {
|
||||
return start;
|
||||
}
|
||||
|
||||
public void setStart(int start) {
|
||||
this.start = start;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return length;
|
||||
}
|
||||
|
||||
public void setLength(int length) {
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
public List<Offer> getOffers() {
|
||||
public List<String> getOffers() {
|
||||
return offers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TabCompleteResponse{" +
|
||||
"transactionId=" + transactionId +
|
||||
", start=" + start +
|
||||
", length=" + length +
|
||||
", offers=" + offers +
|
||||
"offers=" + offers +
|
||||
'}';
|
||||
}
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_13) {
|
||||
this.transactionId = ProtocolUtils.readVarInt(buf);
|
||||
this.start = ProtocolUtils.readVarInt(buf);
|
||||
this.length = ProtocolUtils.readVarInt(buf);
|
||||
int offersAvailable = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < offersAvailable; i++) {
|
||||
String entry = ProtocolUtils.readString(buf);
|
||||
Component component = buf.readBoolean() ? ComponentSerializers.JSON.deserialize(ProtocolUtils.readString(buf)) :
|
||||
null;
|
||||
offers.add(new Offer(entry, component));
|
||||
}
|
||||
} else {
|
||||
int offersAvailable = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < offersAvailable; i++) {
|
||||
offers.add(new Offer(ProtocolUtils.readString(buf), null));
|
||||
}
|
||||
int offersAvailable = ProtocolUtils.readVarInt(buf);
|
||||
for (int i = 0; i < offersAvailable; i++) {
|
||||
offers.add(ProtocolUtils.readString(buf));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
if (protocolVersion >= MINECRAFT_1_13) {
|
||||
ProtocolUtils.writeVarInt(buf, transactionId);
|
||||
ProtocolUtils.writeVarInt(buf, start);
|
||||
ProtocolUtils.writeVarInt(buf, length);
|
||||
ProtocolUtils.writeVarInt(buf, offers.size());
|
||||
for (Offer offer : offers) {
|
||||
ProtocolUtils.writeString(buf, offer.entry);
|
||||
buf.writeBoolean(offer.tooltip != null);
|
||||
if (offer.tooltip != null) {
|
||||
ProtocolUtils.writeString(buf, ComponentSerializers.JSON.serialize(offer.tooltip));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ProtocolUtils.writeVarInt(buf, offers.size());
|
||||
for (Offer offer : offers) {
|
||||
ProtocolUtils.writeString(buf, offer.entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Offer {
|
||||
private final String entry;
|
||||
private final Component tooltip;
|
||||
|
||||
public Offer(String entry, @Nullable Component tooltip) {
|
||||
this.entry = entry;
|
||||
this.tooltip = tooltip;
|
||||
}
|
||||
|
||||
public String getEntry() {
|
||||
return entry;
|
||||
}
|
||||
|
||||
public @Nullable Component getTooltip() {
|
||||
return tooltip;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Offer{" +
|
||||
"entry='" + entry + '\'' +
|
||||
", tooltip=" + tooltip +
|
||||
'}';
|
||||
ProtocolUtils.writeVarInt(buf, offers.size());
|
||||
for (String offer : offers) {
|
||||
ProtocolUtils.writeString(buf, offer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,137 @@
|
||||
package com.velocitypowered.proxy.protocol.packet;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
|
||||
public class TitlePacket implements MinecraftPacket {
|
||||
public static final int SET_TITLE = 0;
|
||||
public static final int SET_SUBTITLE = 1;
|
||||
public static final int SET_ACTION_BAR = 2;
|
||||
public static final int SET_TIMES = 3;
|
||||
public static final int SET_TIMES_OLD = 2;
|
||||
public static final int HIDE = 4;
|
||||
public static final int HIDE_OLD = 3;
|
||||
public static final int RESET = 5;
|
||||
public static final int RESET_OLD = 4;
|
||||
|
||||
private int action;
|
||||
private String component;
|
||||
private int fadeIn;
|
||||
private int stay;
|
||||
private int fadeOut;
|
||||
|
||||
@Override
|
||||
public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
throw new UnsupportedOperationException(); // encode only
|
||||
}
|
||||
|
||||
@Override
|
||||
public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) {
|
||||
ProtocolUtils.writeVarInt(buf, action);
|
||||
if (protocolVersion >= ProtocolConstants.MINECRAFT_1_11) {
|
||||
// 1.11+ shifted the action enum by 1 to handle the action bar
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
case SET_ACTION_BAR:
|
||||
ProtocolUtils.writeString(buf, component);
|
||||
break;
|
||||
case SET_TIMES:
|
||||
buf.writeInt(fadeIn);
|
||||
buf.writeInt(stay);
|
||||
buf.writeInt(fadeOut);
|
||||
break;
|
||||
case HIDE:
|
||||
case RESET:
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
switch (action) {
|
||||
case SET_TITLE:
|
||||
case SET_SUBTITLE:
|
||||
ProtocolUtils.writeString(buf, component);
|
||||
break;
|
||||
case SET_TIMES_OLD:
|
||||
buf.writeInt(fadeIn);
|
||||
buf.writeInt(stay);
|
||||
buf.writeInt(fadeOut);
|
||||
break;
|
||||
case HIDE_OLD:
|
||||
case RESET_OLD:
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public int getAction() {
|
||||
return action;
|
||||
}
|
||||
|
||||
public void setAction(int action) {
|
||||
this.action = action;
|
||||
}
|
||||
|
||||
public String getComponent() {
|
||||
return component;
|
||||
}
|
||||
|
||||
public void setComponent(String component) {
|
||||
this.component = component;
|
||||
}
|
||||
|
||||
public int getFadeIn() {
|
||||
return fadeIn;
|
||||
}
|
||||
|
||||
public void setFadeIn(int fadeIn) {
|
||||
this.fadeIn = fadeIn;
|
||||
}
|
||||
|
||||
public int getStay() {
|
||||
return stay;
|
||||
}
|
||||
|
||||
public void setStay(int stay) {
|
||||
this.stay = stay;
|
||||
}
|
||||
|
||||
public int getFadeOut() {
|
||||
return fadeOut;
|
||||
}
|
||||
|
||||
public void setFadeOut(int fadeOut) {
|
||||
this.fadeOut = fadeOut;
|
||||
}
|
||||
|
||||
public static TitlePacket hideForProtocolVersion(int protocolVersion) {
|
||||
TitlePacket packet = new TitlePacket();
|
||||
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.HIDE : TitlePacket.HIDE_OLD);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static TitlePacket resetForProtocolVersion(int protocolVersion) {
|
||||
TitlePacket packet = new TitlePacket();
|
||||
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.RESET : TitlePacket.RESET_OLD);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public static TitlePacket timesForProtocolVersion(int protocolVersion) {
|
||||
TitlePacket packet = new TitlePacket();
|
||||
packet.setAction(protocolVersion >= ProtocolConstants.MINECRAFT_1_11 ? TitlePacket.SET_TIMES : TitlePacket.SET_TIMES_OLD);
|
||||
return packet;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TitlePacket{" +
|
||||
"action=" + action +
|
||||
", component='" + component + '\'' +
|
||||
", fadeIn=" + fadeIn +
|
||||
", stay=" + stay +
|
||||
", fadeOut=" + fadeOut +
|
||||
'}';
|
||||
}
|
||||
}
|
@@ -5,7 +5,6 @@ import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolUtils;
|
||||
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.buffer.Unpooled;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -20,13 +19,20 @@ public enum PluginMessageUtil {
|
||||
return message.getChannel().equals("MC|Brand") || message.getChannel().equals("minecraft:brand");
|
||||
}
|
||||
|
||||
public static boolean isMCRegister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals("REGISTER") || message.getChannel().equals("minecraft:register");
|
||||
}
|
||||
|
||||
public static boolean isMCUnregister(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
return message.getChannel().equals("UNREGISTER") || message.getChannel().equals("minecraft:unregister");
|
||||
}
|
||||
|
||||
public static List<String> getChannels(PluginMessage message) {
|
||||
Preconditions.checkNotNull(message, "message");
|
||||
Preconditions.checkArgument(message.getChannel().equals("REGISTER") ||
|
||||
message.getChannel().equals("UNREGISTER") ||
|
||||
message.getChannel().equals("minecraft:register") ||
|
||||
message.getChannel().equals("minecraft:unregister"),
|
||||
"Unknown channel type " + message.getChannel());
|
||||
Preconditions.checkArgument(isMCRegister(message) || isMCUnregister(message),"Unknown channel type %s",
|
||||
message.getChannel());
|
||||
String channels = new String(message.getData(), StandardCharsets.UTF_8);
|
||||
return ImmutableList.copyOf(channels.split("\0"));
|
||||
}
|
||||
|
@@ -1,24 +0,0 @@
|
||||
package com.velocitypowered.proxy.protocol.util;
|
||||
|
||||
import com.velocitypowered.proxy.protocol.packet.ScoreboardObjective;
|
||||
|
||||
public class ScoreboardProtocolUtil {
|
||||
private ScoreboardProtocolUtil() {
|
||||
throw new AssertionError();
|
||||
}
|
||||
|
||||
public static ScoreboardObjective.ObjectiveMode getMode(String mode) {
|
||||
return ScoreboardObjective.ObjectiveMode.valueOf(mode.toUpperCase());
|
||||
}
|
||||
|
||||
public static ScoreboardObjective.ObjectiveMode getMode(int enumVal) {
|
||||
switch (enumVal) {
|
||||
case 0:
|
||||
return ScoreboardObjective.ObjectiveMode.INTEGER;
|
||||
case 1:
|
||||
return ScoreboardObjective.ObjectiveMode.HEARTS;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown mode " + enumVal);
|
||||
}
|
||||
}
|
||||
}
|
@@ -16,7 +16,6 @@ import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.concurrent.*;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
|
||||
public class VelocityScheduler implements Scheduler {
|
||||
private final PluginManager pluginManager;
|
||||
@@ -66,13 +65,13 @@ public class VelocityScheduler implements Scheduler {
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder delay(int time, TimeUnit unit) {
|
||||
public TaskBuilder delay(long time, TimeUnit unit) {
|
||||
this.delay = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskBuilder repeat(int time, TimeUnit unit) {
|
||||
public TaskBuilder repeat(long time, TimeUnit unit) {
|
||||
this.repeat = unit.toMillis(time);
|
||||
return this;
|
||||
}
|
||||
@@ -91,62 +90,9 @@ public class VelocityScheduler implements Scheduler {
|
||||
|
||||
@Override
|
||||
public ScheduledTask schedule() {
|
||||
if (delay == 0 && repeat == 0) {
|
||||
// A special purpose, simplified implementation
|
||||
VelocityImmediatelyScheduledTask task = new VelocityImmediatelyScheduledTask(plugin, runnable);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
taskService.execute(task);
|
||||
return task;
|
||||
} else {
|
||||
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private class VelocityImmediatelyScheduledTask implements ScheduledTask, Runnable {
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private final AtomicReference<TaskStatus> status;
|
||||
private Thread taskThread;
|
||||
|
||||
private VelocityImmediatelyScheduledTask(Object plugin, Runnable runnable) {
|
||||
this.plugin = plugin;
|
||||
this.runnable = runnable;
|
||||
this.status = new AtomicReference<>(TaskStatus.SCHEDULED);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object plugin() {
|
||||
return plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public TaskStatus status() {
|
||||
return status.get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
if (status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.CANCELLED)) {
|
||||
if (taskThread != null) {
|
||||
taskThread.interrupt();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
taskThread = Thread.currentThread();
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
|
||||
}
|
||||
status.compareAndSet(TaskStatus.SCHEDULED, TaskStatus.FINISHED);
|
||||
taskThread = null;
|
||||
tasksByPlugin.remove(plugin, this);
|
||||
VelocityTask task = new VelocityTask(plugin, runnable, delay, repeat);
|
||||
tasksByPlugin.put(plugin, task);
|
||||
return task;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,6 +100,7 @@ public class VelocityScheduler implements Scheduler {
|
||||
private final Object plugin;
|
||||
private final Runnable runnable;
|
||||
private ScheduledFuture<?> future;
|
||||
private volatile Thread currentTaskThread;
|
||||
|
||||
private VelocityTask(Object plugin, Runnable runnable, long delay, long repeat) {
|
||||
this.plugin = plugin;
|
||||
@@ -191,6 +138,12 @@ public class VelocityScheduler implements Scheduler {
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(false);
|
||||
|
||||
Thread cur = currentTaskThread;
|
||||
if (cur != null) {
|
||||
cur.interrupt();
|
||||
}
|
||||
|
||||
onFinish();
|
||||
}
|
||||
}
|
||||
@@ -198,6 +151,7 @@ public class VelocityScheduler implements Scheduler {
|
||||
@Override
|
||||
public void run() {
|
||||
taskService.execute(() -> {
|
||||
currentTaskThread = Thread.currentThread();
|
||||
try {
|
||||
runnable.run();
|
||||
} catch (Exception e) {
|
||||
@@ -208,6 +162,7 @@ public class VelocityScheduler implements Scheduler {
|
||||
Log.logger.error("Exception in task {} by plugin {}", runnable, plugin);
|
||||
}
|
||||
}
|
||||
currentTaskThread = null;
|
||||
});
|
||||
}
|
||||
|
||||
|
@@ -0,0 +1,49 @@
|
||||
package com.velocitypowered.proxy.server;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ServerMap {
|
||||
private final VelocityServer server;
|
||||
private final Map<String, RegisteredServer> servers = new ConcurrentHashMap<>();
|
||||
|
||||
public ServerMap(VelocityServer server) {
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
public Optional<RegisteredServer> getServer(String name) {
|
||||
Preconditions.checkNotNull(name, "server");
|
||||
String lowerName = name.toLowerCase(Locale.US);
|
||||
return Optional.ofNullable(servers.get(lowerName));
|
||||
}
|
||||
|
||||
public Collection<RegisteredServer> getAllServers() {
|
||||
return ImmutableList.copyOf(servers.values());
|
||||
}
|
||||
|
||||
public RegisteredServer register(ServerInfo serverInfo) {
|
||||
Preconditions.checkNotNull(serverInfo, "serverInfo");
|
||||
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
|
||||
VelocityRegisteredServer rs = new VelocityRegisteredServer(server, serverInfo);
|
||||
Preconditions.checkArgument(servers.putIfAbsent(lowerName, rs) == null, "Server with name %s already registered", serverInfo.getName());
|
||||
return rs;
|
||||
}
|
||||
|
||||
public void unregister(ServerInfo serverInfo) {
|
||||
Preconditions.checkNotNull(serverInfo, "serverInfo");
|
||||
String lowerName = serverInfo.getName().toLowerCase(Locale.US);
|
||||
RegisteredServer rs = servers.get(lowerName);
|
||||
Preconditions.checkArgument(rs != null, "Server with name %s is not registered!", serverInfo.getName());
|
||||
Preconditions.checkArgument(rs.getServerInfo().equals(serverInfo), "Trying to remove server %s with differing information", serverInfo.getName());
|
||||
Preconditions.checkState(servers.remove(lowerName, rs), "Server with name %s replaced whilst unregistering", serverInfo.getName());
|
||||
}
|
||||
}
|
@@ -0,0 +1,112 @@
|
||||
package com.velocitypowered.proxy.server;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import com.velocitypowered.api.proxy.ServerConnection;
|
||||
import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintFrameDecoder;
|
||||
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
|
||||
import com.velocitypowered.proxy.server.ping.PingSessionHandler;
|
||||
import io.netty.channel.Channel;
|
||||
import io.netty.channel.ChannelFuture;
|
||||
import io.netty.channel.ChannelFutureListener;
|
||||
import io.netty.channel.ChannelInitializer;
|
||||
import io.netty.handler.timeout.ReadTimeoutHandler;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
|
||||
public class VelocityRegisteredServer implements RegisteredServer {
|
||||
private final VelocityServer server;
|
||||
private final ServerInfo serverInfo;
|
||||
private final Set<ConnectedPlayer> players = ConcurrentHashMap.newKeySet();
|
||||
|
||||
public VelocityRegisteredServer(VelocityServer server, ServerInfo serverInfo) {
|
||||
this.server = server;
|
||||
this.serverInfo = serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ServerInfo getServerInfo() {
|
||||
return serverInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Collection<Player> getPlayersConnected() {
|
||||
return ImmutableList.copyOf(players);
|
||||
}
|
||||
|
||||
@Override
|
||||
public CompletableFuture<ServerPing> ping() {
|
||||
CompletableFuture<ServerPing> pingFuture = new CompletableFuture<>();
|
||||
server.initializeGenericBootstrap()
|
||||
.handler(new ChannelInitializer<Channel>() {
|
||||
@Override
|
||||
protected void initChannel(Channel ch) throws Exception {
|
||||
ch.pipeline()
|
||||
.addLast(READ_TIMEOUT, new ReadTimeoutHandler(server.getConfiguration().getReadTimeout(), TimeUnit.SECONDS))
|
||||
.addLast(FRAME_DECODER, new MinecraftVarintFrameDecoder())
|
||||
.addLast(FRAME_ENCODER, MinecraftVarintLengthEncoder.INSTANCE)
|
||||
.addLast(MINECRAFT_DECODER, new MinecraftDecoder(ProtocolConstants.Direction.CLIENTBOUND))
|
||||
.addLast(MINECRAFT_ENCODER, new MinecraftEncoder(ProtocolConstants.Direction.SERVERBOUND));
|
||||
|
||||
MinecraftConnection connection = new MinecraftConnection(ch, server);
|
||||
connection.setState(StateRegistry.HANDSHAKE);
|
||||
ch.pipeline().addLast(HANDLER, connection);
|
||||
}
|
||||
})
|
||||
.connect(serverInfo.getAddress())
|
||||
.addListener(new ChannelFutureListener() {
|
||||
@Override
|
||||
public void operationComplete(ChannelFuture future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
MinecraftConnection conn = future.channel().pipeline().get(MinecraftConnection.class);
|
||||
conn.setSessionHandler(new PingSessionHandler(pingFuture, VelocityRegisteredServer.this, conn));
|
||||
} else {
|
||||
pingFuture.completeExceptionally(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
return pingFuture;
|
||||
}
|
||||
|
||||
public void addPlayer(ConnectedPlayer player) {
|
||||
players.add(player);
|
||||
}
|
||||
|
||||
public void removePlayer(ConnectedPlayer player) {
|
||||
players.remove(player);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
|
||||
for (ConnectedPlayer player : players) {
|
||||
if (player.getConnectedServer() != null && player.getConnectedServer().getServerInfo().equals(serverInfo)) {
|
||||
ServerConnection connection = player.getConnectedServer();
|
||||
return connection.sendPluginMessage(identifier, data);
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "registered server: " + serverInfo;
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
package com.velocitypowered.proxy.server.ping;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerPing;
|
||||
import com.velocitypowered.proxy.VelocityServer;
|
||||
import com.velocitypowered.proxy.connection.MinecraftConnection;
|
||||
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
|
||||
import com.velocitypowered.proxy.protocol.MinecraftPacket;
|
||||
import com.velocitypowered.proxy.protocol.ProtocolConstants;
|
||||
import com.velocitypowered.proxy.protocol.StateRegistry;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusRequest;
|
||||
import com.velocitypowered.proxy.protocol.packet.StatusResponse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
public class PingSessionHandler implements MinecraftSessionHandler {
|
||||
private final CompletableFuture<ServerPing> result;
|
||||
private final RegisteredServer server;
|
||||
private final MinecraftConnection connection;
|
||||
private boolean completed = false;
|
||||
|
||||
public PingSessionHandler(CompletableFuture<ServerPing> result, RegisteredServer server, MinecraftConnection connection) {
|
||||
this.result = result;
|
||||
this.server = server;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void activated() {
|
||||
Handshake handshake = new Handshake();
|
||||
handshake.setNextStatus(StateRegistry.STATUS_ID);
|
||||
handshake.setServerAddress(server.getServerInfo().getAddress().getHostString());
|
||||
handshake.setPort(server.getServerInfo().getAddress().getPort());
|
||||
handshake.setProtocolVersion(ProtocolConstants.MINIMUM_GENERIC_VERSION);
|
||||
connection.write(handshake);
|
||||
|
||||
connection.setState(StateRegistry.STATUS);
|
||||
connection.write(StatusRequest.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(MinecraftPacket packet) {
|
||||
Preconditions.checkState(packet instanceof StatusResponse, "Did not get status response back from connection");
|
||||
|
||||
// All good!
|
||||
completed = true;
|
||||
connection.close();
|
||||
|
||||
ServerPing ping = VelocityServer.GSON.fromJson(((StatusResponse) packet).getStatus(), ServerPing.class);
|
||||
result.complete(ping);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void disconnected() {
|
||||
if (!completed) {
|
||||
result.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void exception(Throwable throwable) {
|
||||
completed = true;
|
||||
result.completeExceptionally(throwable);
|
||||
}
|
||||
}
|
@@ -1,56 +0,0 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.google.common.base.Preconditions;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
|
||||
import java.util.*;
|
||||
import java.util.concurrent.locks.ReadWriteLock;
|
||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||
|
||||
public class ServerMap {
|
||||
private final Map<String, ServerInfo> servers = new HashMap<>();
|
||||
private final ReadWriteLock lock = new ReentrantReadWriteLock();
|
||||
|
||||
public Optional<ServerInfo> getServer(String server) {
|
||||
Preconditions.checkNotNull(server, "server");
|
||||
String lowerName = server.toLowerCase(Locale.US);
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return Optional.ofNullable(servers.get(lowerName));
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public Collection<ServerInfo> getAllServers() {
|
||||
lock.readLock().lock();
|
||||
try {
|
||||
return ImmutableList.copyOf(servers.values());
|
||||
} finally {
|
||||
lock.readLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void register(ServerInfo server) {
|
||||
Preconditions.checkNotNull(server, "server");
|
||||
String lowerName = server.getName().toLowerCase(Locale.US);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
Preconditions.checkArgument(servers.putIfAbsent(lowerName, server) == null, "Server with name %s already registered", server.getName());
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
|
||||
public void unregister(ServerInfo server) {
|
||||
Preconditions.checkNotNull(server, "server");
|
||||
String lowerName = server.getName().toLowerCase(Locale.US);
|
||||
lock.writeLock().lock();
|
||||
try {
|
||||
Preconditions.checkArgument(servers.remove(lowerName, server), "Server with this name is not registered!");
|
||||
} finally {
|
||||
lock.writeLock().unlock();
|
||||
}
|
||||
}
|
||||
}
|
@@ -6,7 +6,6 @@ import com.google.common.collect.MapMaker;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
|
||||
/**
|
||||
|
@@ -3,9 +3,7 @@ package com.velocitypowered.proxy.protocol;
|
||||
import com.velocitypowered.proxy.protocol.packet.Handshake;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_1;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.MINECRAFT_1_12_2;
|
||||
import static com.velocitypowered.proxy.protocol.ProtocolConstants.*;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class PacketRegistryTest {
|
||||
|
@@ -9,7 +9,7 @@ import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class VelocitySchedulerTest {
|
||||
// TODO: The timings here will be inaccurate on slow systems. Need to find a testing-friendly replacement for Thread.sleep()
|
||||
|
@@ -7,7 +7,8 @@ import java.net.InetAddress;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class RatelimiterTest {
|
||||
|
||||
|
@@ -1,31 +1,34 @@
|
||||
package com.velocitypowered.proxy.util;
|
||||
|
||||
import com.velocitypowered.api.proxy.server.RegisteredServer;
|
||||
import com.velocitypowered.api.proxy.server.ServerInfo;
|
||||
import com.velocitypowered.proxy.server.ServerMap;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ServerMapTest {
|
||||
private static final InetSocketAddress TEST_ADDRESS = new InetSocketAddress(InetAddress.getLoopbackAddress(), 25565);
|
||||
|
||||
@Test
|
||||
void respectsCaseInsensitivity() {
|
||||
ServerMap map = new ServerMap();
|
||||
ServerMap map = new ServerMap(null);
|
||||
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
|
||||
map.register(info);
|
||||
RegisteredServer connection = map.register(info);
|
||||
|
||||
assertEquals(Optional.of(info), map.getServer("TestServer"));
|
||||
assertEquals(Optional.of(info), map.getServer("testserver"));
|
||||
assertEquals(Optional.of(info), map.getServer("TESTSERVER"));
|
||||
assertEquals(Optional.of(connection), map.getServer("TestServer"));
|
||||
assertEquals(Optional.of(connection), map.getServer("testserver"));
|
||||
assertEquals(Optional.of(connection), map.getServer("TESTSERVER"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void rejectsRepeatedRegisterAttempts() {
|
||||
ServerMap map = new ServerMap();
|
||||
ServerMap map = new ServerMap(null);
|
||||
ServerInfo info = new ServerInfo("TestServer", TEST_ADDRESS);
|
||||
map.register(info);
|
||||
|
||||
|
Reference in New Issue
Block a user