13
0
geforkt von Mirrors/Velocity

Merge branch 'dev/1.1.0' into decode-multiple

Dieser Commit ist enthalten in:
Andrew Steinborn 2019-11-27 01:09:21 -05:00
Commit ae82bb21af
19 geänderte Dateien mit 822 neuen und 140 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,51 @@
package com.velocitypowered.api.event.player;
import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.util.Optional;
import org.checkerframework.checker.nullness.qual.Nullable;
/**
* Fired when a player has finished connecting to the proxy and we need to choose the first server
* to connect to.
*/
public class PlayerChooseInitialServerEvent {
private final Player player;
private @Nullable RegisteredServer initialServer;
/**
* Constructs a PlayerChooseInitialServerEvent.
* @param player the player that was connected
* @param initialServer the initial server selected, may be {@code null}
*/
public PlayerChooseInitialServerEvent(Player player, @Nullable RegisteredServer initialServer) {
this.player = Preconditions.checkNotNull(player, "player");
this.initialServer = initialServer;
}
public Player getPlayer() {
return player;
}
public Optional<RegisteredServer> getInitialServer() {
return Optional.ofNullable(initialServer);
}
/**
* Sets the new initial server.
* @param server the initial server the player should connect to
*/
public void setInitialServer(RegisteredServer server) {
this.initialServer = server;
}
@Override
public String toString() {
return "PlayerChooseInitialServerEvent{"
+ "player=" + player
+ ", initialServer=" + initialServer
+ '}';
}
}

Datei anzeigen

@ -33,7 +33,8 @@ public enum ProtocolVersion {
MINECRAFT_1_14_1(480, "1.14.1"),
MINECRAFT_1_14_2(485, "1.14.2"),
MINECRAFT_1_14_3(490, "1.14.3"),
MINECRAFT_1_14_4(498, "1.14.4");
MINECRAFT_1_14_4(498, "1.14.4"),
MINECRAFT_1_15(566, "1.15-pre2");
private final int protocol;
private final String name;

Datei anzeigen

@ -72,7 +72,10 @@ public class VelocityCommand implements Command {
@Override
public List<String> suggest(CommandSource source, String @NonNull [] currentArgs) {
if (currentArgs.length == 0) {
return ImmutableList.copyOf(subcommands.keySet());
return subcommands.entrySet().stream()
.filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey)
.collect(ImmutableList.toImmutableList());
}
if (currentArgs.length == 1) {
@ -81,7 +84,7 @@ public class VelocityCommand implements Command {
currentArgs[0].length()))
.filter(e -> e.getValue().hasPermission(source, new String[0]))
.map(Map.Entry::getKey)
.collect(Collectors.toList());
.collect(ImmutableList.toImmutableList());
}
Command command = subcommands.get(currentArgs[0].toLowerCase(Locale.US));

Datei anzeigen

@ -100,7 +100,7 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
" configuration is used if no servers could be contacted."
})
@ConfigKey("ping-passthrough")
private PingPassthroughMode pingPassthrough;
private PingPassthroughMode pingPassthrough = PingPassthroughMode.DISABLED;
@Table("[servers]")
private final Servers servers;
@ -192,44 +192,38 @@ public class VelocityConfiguration extends AnnotatedConfig implements ProxyConfi
}
if (servers.getServers().isEmpty()) {
logger.error("You have no servers configured. :(");
valid = false;
} else {
if (servers.getAttemptConnectionOrder().isEmpty()) {
logger.error("No fallback servers are configured!");
logger.warn("You don't have any servers configured.");
}
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
try {
AddressUtil.parseAddress(entry.getValue());
} catch (IllegalArgumentException e) {
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
valid = false;
}
}
for (Map.Entry<String, String> entry : servers.getServers().entrySet()) {
try {
AddressUtil.parseAddress(entry.getValue());
} catch (IllegalArgumentException e) {
logger.error("Server {} does not have a valid IP address.", entry.getKey(), e);
valid = false;
}
for (String s : servers.getAttemptConnectionOrder()) {
if (!servers.getServers().containsKey(s)) {
logger.error("Fallback server " + s + " is not registered in your configuration!");
valid = false;
}
}
for (Map.Entry<String, List<String>> entry : forcedHosts.getForcedHosts().entrySet()) {
if (entry.getValue().isEmpty()) {
logger.error("Forced host '{}' does not contain any servers", entry.getKey());
valid = false;
continue;
}
for (String s : servers.getAttemptConnectionOrder()) {
if (!servers.getServers().containsKey(s)) {
logger.error("Fallback server " + s + " is not registered in your configuration!");
for (String server : entry.getValue()) {
if (!servers.getServers().containsKey(server)) {
logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
valid = false;
}
}
for (Map.Entry<String, List<String>> entry : forcedHosts.getForcedHosts().entrySet()) {
if (entry.getValue().isEmpty()) {
logger.error("Forced host '{}' does not contain any servers", entry.getKey());
valid = false;
continue;
}
for (String server : entry.getValue()) {
if (!servers.getServers().containsKey(server)) {
logger.error("Server '{}' for forced host '{}' does not exist", server, entry.getKey());
valid = false;
}
}
}
}
try {

Datei anzeigen

@ -1,5 +1,8 @@
package com.velocitypowered.proxy.connection.backend;
import static com.velocitypowered.proxy.connection.backend.BungeeCordMessageResponder.getBungeeCordChannel;
import com.google.common.collect.ImmutableList;
import com.mojang.brigadier.arguments.StringArgumentType;
import com.mojang.brigadier.builder.LiteralArgumentBuilder;
import com.mojang.brigadier.builder.RequiredArgumentBuilder;
@ -31,6 +34,7 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
private final VelocityServerConnection serverConn;
private final ClientPlaySessionHandler playerSessionHandler;
private final MinecraftConnection playerConnection;
private final BungeeCordMessageResponder bungeecordMessageResponder;
private boolean exceptionTriggered = false;
BackendPlaySessionHandler(VelocityServer server, VelocityServerConnection serverConn) {
@ -44,11 +48,18 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
"Initializing BackendPlaySessionHandler with no backing client play session handler!");
}
this.playerSessionHandler = (ClientPlaySessionHandler) psh;
this.bungeecordMessageResponder = new BungeeCordMessageResponder(server,
serverConn.getPlayer());
}
@Override
public void activated() {
serverConn.getServer().addPlayer(serverConn.getPlayer());
MinecraftConnection serverMc = serverConn.ensureConnected();
serverMc.write(PluginMessageUtil.constructChannelsPacket(serverMc.getProtocolVersion(),
ImmutableList.of(getBungeeCordChannel(serverMc.getProtocolVersion()))
));
}
@Override
@ -86,6 +97,10 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(PluginMessage packet) {
if (bungeecordMessageResponder.process(packet)) {
return true;
}
if (!serverConn.getPlayer().canForwardPluginMessage(serverConn.ensureConnected()
.getProtocolVersion(), packet)) {
return true;

Datei anzeigen

@ -0,0 +1,329 @@
package com.velocitypowered.proxy.connection.backend;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.UuidUtils;
import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.packet.PluginMessage;
import com.velocitypowered.proxy.protocol.util.ByteBufDataInput;
import com.velocitypowered.proxy.protocol.util.ByteBufDataOutput;
import com.velocitypowered.proxy.server.VelocityRegisteredServer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.util.StringJoiner;
import net.kyori.text.serializer.legacy.LegacyComponentSerializer;
class BungeeCordMessageResponder {
private static final MinecraftChannelIdentifier MODERN_CHANNEL = MinecraftChannelIdentifier
.create("bungeecord", "main");
private static final LegacyChannelIdentifier LEGACY_CHANNEL =
new LegacyChannelIdentifier("BungeeCord");
private final VelocityServer proxy;
private final ConnectedPlayer player;
BungeeCordMessageResponder(VelocityServer proxy, ConnectedPlayer player) {
this.proxy = proxy;
this.player = player;
}
private void processConnect(ByteBufDataInput in) {
String serverName = in.readUTF();
proxy.getServer(serverName).ifPresent(server -> player.createConnectionRequest(server)
.fireAndForget());
}
private void processConnectOther(ByteBufDataInput in) {
String playerName = in.readUTF();
String serverName = in.readUTF();
proxy.getPlayer(playerName).flatMap(player -> proxy.getServer(serverName))
.ifPresent(server -> player.createConnectionRequest(server).fireAndForget());
}
private void processIp(ByteBufDataInput in) {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("IP");
out.writeUTF(player.getRemoteAddress().getHostString());
out.writeInt(player.getRemoteAddress().getPort());
sendResponseOnConnection(buf);
}
private void processPlayerCount(ByteBufDataInput in) {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
String target = in.readUTF();
if (target.equals("ALL")) {
out.writeUTF("PlayerCount");
out.writeUTF("ALL");
out.writeInt(proxy.getPlayerCount());
} else {
proxy.getServer(target).ifPresent(rs -> {
int playersOnServer = rs.getPlayersConnected().size();
out.writeUTF("PlayerCount");
out.writeUTF(rs.getServerInfo().getName());
out.writeInt(playersOnServer);
});
}
if (buf.isReadable()) {
sendResponseOnConnection(buf);
} else {
buf.release();
}
}
private void processPlayerList(ByteBufDataInput in) {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
String target = in.readUTF();
if (target.equals("ALL")) {
out.writeUTF("PlayerList");
out.writeUTF("ALL");
StringJoiner joiner = new StringJoiner(", ");
for (Player online : proxy.getAllPlayers()) {
joiner.add(online.getUsername());
}
out.writeUTF(joiner.toString());
} else {
proxy.getServer(target).ifPresent(info -> {
out.writeUTF("PlayerList");
out.writeUTF(info.getServerInfo().getName());
StringJoiner joiner = new StringJoiner(", ");
for (Player online : info.getPlayersConnected()) {
joiner.add(online.getUsername());
}
out.writeUTF(joiner.toString());
});
}
if (buf.isReadable()) {
sendResponseOnConnection(buf);
} else {
buf.release();
}
}
private void processGetServers() {
StringJoiner joiner = new StringJoiner(", ");
for (RegisteredServer server : proxy.getAllServers()) {
joiner.add(server.getServerInfo().getName());
}
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("GetServers");
out.writeUTF(joiner.toString());
sendResponseOnConnection(buf);
}
private void processMessage(ByteBufDataInput in) {
String target = in.readUTF();
String message = in.readUTF();
if (target.equals("ALL")) {
for (Player player : proxy.getAllPlayers()) {
player.sendMessage(LegacyComponentSerializer.INSTANCE.deserialize(message));
}
} else {
proxy.getPlayer(target).ifPresent(player -> {
player.sendMessage(LegacyComponentSerializer.INSTANCE.deserialize(message));
});
}
}
private void processGetServer() {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("GetServer");
out.writeUTF(player.ensureAndGetCurrentServer().getServerInfo().getName());
sendResponseOnConnection(buf);
}
private void processUuid() {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("UUID");
out.writeUTF(UuidUtils.toUndashed(player.getUniqueId()));
sendResponseOnConnection(buf);
}
private void processUuidOther(ByteBufDataInput in) {
proxy.getPlayer(in.readUTF()).ifPresent(player -> {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("UUIDOther");
out.writeUTF(player.getUsername());
out.writeUTF(UuidUtils.toUndashed(player.getUniqueId()));
sendResponseOnConnection(buf);
});
}
private void processServerIp(ByteBufDataInput in) {
proxy.getServer(in.readUTF()).ifPresent(info -> {
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput out = new ByteBufDataOutput(buf);
out.writeUTF("ServerIP");
out.writeUTF(info.getServerInfo().getName());
out.writeUTF(info.getServerInfo().getAddress().getHostString());
out.writeShort(info.getServerInfo().getAddress().getPort());
sendResponseOnConnection(buf);
});
}
private void processKick(ByteBufDataInput in) {
proxy.getPlayer(in.readUTF()).ifPresent(player -> {
String kickReason = in.readUTF();
player.disconnect(LegacyComponentSerializer.INSTANCE.deserialize(kickReason));
});
}
private ByteBuf prepareForwardMessage(ByteBufDataInput in) {
String channel = in.readUTF();
short messageLength = in.readShort();
ByteBuf buf = Unpooled.buffer();
ByteBufDataOutput forwarded = new ByteBufDataOutput(buf);
forwarded.writeUTF(channel);
forwarded.writeShort(messageLength);
buf.writeBytes(in.unwrap().readSlice(messageLength));
return buf;
}
private void processForwardToPlayer(ByteBufDataInput in) {
proxy.getPlayer(in.readUTF())
.flatMap(Player::getCurrentServer)
.ifPresent(server -> sendServerResponse(player, prepareForwardMessage(in)));
}
private void processForwardToServer(ByteBufDataInput in) {
ByteBuf toForward = prepareForwardMessage(in);
String target = in.readUTF();
if (target.equals("ALL")) {
ByteBuf unreleasableForward = Unpooled.unreleasableBuffer(toForward);
try {
for (RegisteredServer rs : proxy.getAllServers()) {
((VelocityRegisteredServer) rs).sendPluginMessage(LEGACY_CHANNEL, unreleasableForward);
}
} finally {
toForward.release();
}
} else {
proxy.getServer(target).ifPresent(rs -> ((VelocityRegisteredServer) rs)
.sendPluginMessage(LEGACY_CHANNEL, toForward));
}
}
// Note: this method will always release the buffer!
private void sendResponseOnConnection(ByteBuf buf) {
sendServerResponse(this.player, buf);
}
static String getBungeeCordChannel(ProtocolVersion version) {
return version.compareTo(ProtocolVersion.MINECRAFT_1_13) >= 0 ? MODERN_CHANNEL.getId()
: LEGACY_CHANNEL.getId();
}
// Note: this method will always release the buffer!
private static void sendServerResponse(ConnectedPlayer player, ByteBuf buf) {
MinecraftConnection serverConnection = player.ensureAndGetCurrentServer().ensureConnected();
String chan = getBungeeCordChannel(serverConnection.getProtocolVersion());
PluginMessage msg = null;
boolean released = false;
try {
VelocityServerConnection vsc = player.getConnectedServer();
if (vsc == null) {
return;
}
MinecraftConnection serverConn = vsc.ensureConnected();
msg = new PluginMessage(chan, buf);
serverConn.write(msg);
released = true;
} finally {
if (!released && msg != null) {
msg.release();
}
}
}
boolean process(PluginMessage message) {
if (!MODERN_CHANNEL.getId().equals(message.getChannel()) && !LEGACY_CHANNEL.getId()
.equals(message.getChannel())) {
return false;
}
ByteBufDataInput in = new ByteBufDataInput(message.content());
String subChannel = in.readUTF();
switch (subChannel) {
case "ForwardToPlayer":
this.processForwardToPlayer(in);
break;
case "Forward":
this.processForwardToServer(in);
break;
case "Connect":
this.processConnect(in);
break;
case "ConnectOther":
this.processConnectOther(in);
break;
case "IP":
this.processIp(in);
break;
case "PlayerCount":
this.processPlayerCount(in);
break;
case "PlayerList":
this.processPlayerList(in);
break;
case "GetServers":
this.processGetServers();
break;
case "Message":
this.processMessage(in);
break;
case "GetServer":
this.processGetServer();
break;
case "UUID":
this.processUuid();
break;
case "UUIDOther":
this.processUuidOther(in);
break;
case "ServerIP":
this.processServerIp(in);
break;
case "KickPlayer":
this.processKick(in);
break;
default:
// Do nothing, unknown command
break;
}
return true;
}
}

Datei anzeigen

@ -17,9 +17,9 @@ import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
import com.velocitypowered.proxy.protocol.packet.SetCompression;
import com.velocitypowered.proxy.util.except.QuietException;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.concurrent.CompletableFuture;
@ -111,8 +111,19 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override
public void disconnected() {
resultFuture
.completeExceptionally(new IOException("Unexpectedly disconnected from remote server"));
if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.LEGACY) {
resultFuture.completeExceptionally(
new QuietException("The connection to the remote server was unexpectedly closed.\n"
+ "This is usually because the remote server does not have BungeeCord IP forwarding "
+ "correctly enabled.\nSee "
+ "https://docs.velocitypowered.com/en/latest/users/player-info-forwarding.html "
+ "for instructions on how to configure player info forwarding correctly.")
);
} else {
resultFuture.completeExceptionally(
new QuietException("The connection to the remote server was unexpectedly closed.")
);
}
}
private static ByteBuf createForwardingData(byte[] hmacSecret, String address,

Datei anzeigen

@ -34,6 +34,7 @@ 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.server.VelocityRegisteredServer;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFutureListener;
@ -210,12 +211,22 @@ public class VelocityServerConnection implements MinecraftConnectionAssociation,
@Override
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data));
}
/**
* Sends a plugin message to the server through this connection.
* @param identifier the channel ID to use
* @param data the data
* @return whether or not the message was sent
*/
public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) {
Preconditions.checkNotNull(identifier, "identifier");
Preconditions.checkNotNull(data, "data");
MinecraftConnection mc = ensureConnected();
PluginMessage message = new PluginMessage(identifier.getId(), Unpooled.wrappedBuffer(data));
PluginMessage message = new PluginMessage(identifier.getId(), data);
mc.write(message);
return true;
}

Datei anzeigen

@ -322,8 +322,8 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
joinGame.setDimension(getFakeTemporaryDimensionId(realDim));
player.getConnection().delayedWrite(joinGame);
player.getConnection().delayedWrite(
new Respawn(realDim, joinGame.getDifficulty(), joinGame.getGamemode(),
joinGame.getLevelType()));
new Respawn(realDim, joinGame.getPartialHashedSeed(), joinGame.getDifficulty(),
joinGame.getGamemode(), joinGame.getLevelType()));
}
// Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to
@ -392,31 +392,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
}
List<Offer> offers = new ArrayList<>();
int longestLength = 0;
for (String suggestion : suggestions) {
offers.add(new Offer(suggestion));
if (suggestion.length() > longestLength) {
longestLength = suggestion.length();
}
}
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
int startPos = packet.getCommand().lastIndexOf(' ') + 1;
int length;
if (startPos == 0) {
startPos = packet.getCommand().length() + 1;
length = longestLength;
} else {
length = packet.getCommand().length() - startPos;
if (startPos > 0) {
TabCompleteResponse resp = new TabCompleteResponse();
resp.setTransactionId(packet.getTransactionId());
resp.setStart(startPos);
resp.setLength(packet.getCommand().length() - startPos);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
}
resp.setStart(startPos);
resp.setLength(length);
resp.getOffers().addAll(offers);
player.getConnection().write(resp);
return true;
}

Datei anzeigen

@ -135,6 +135,18 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return Optional.ofNullable(connectedServer);
}
/**
* Makes sure the player is connected to a server and returns the server they are connected to.
* @return the server the player is connected to
*/
public VelocityServerConnection ensureAndGetCurrentServer() {
VelocityServerConnection con = this.connectedServer;
if (con == null) {
throw new IllegalStateException("Not connected to server!");
}
return con;
}
@Override
public GameProfile getGameProfile() {
return profile;
@ -417,33 +429,22 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player {
return;
}
if (connectedServer == null) {
Optional<RegisteredServer> nextServer = getNextServerToTry(rs);
if (nextServer.isPresent()) {
// There can't be any connection in flight now.
resetInFlightConnection();
createConnectionRequest(nextServer.get()).fireAndForget();
} else {
disconnect(friendlyReason);
}
boolean kickedFromCurrent = connectedServer == null || connectedServer.getServer().equals(rs);
ServerKickResult result;
if (kickedFromCurrent) {
Optional<RegisteredServer> next = getNextServerToTry(rs);
result = next.<ServerKickResult>map(RedirectPlayer::create)
.orElseGet(() -> DisconnectPlayer.create(friendlyReason));
} else {
boolean kickedFromCurrent = connectedServer.getServer().equals(rs);
ServerKickResult result;
if (kickedFromCurrent) {
Optional<RegisteredServer> next = getNextServerToTry(rs);
result = next.<ServerKickResult>map(RedirectPlayer::create)
.orElseGet(() -> DisconnectPlayer.create(friendlyReason));
} else {
// If we were kicked by going to another server, the connection should not be in flight
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
resetInFlightConnection();
}
result = Notify.create(friendlyReason);
// If we were kicked by going to another server, the connection should not be in flight
if (connectionInFlight != null && connectionInFlight.getServer().equals(rs)) {
resetInFlightConnection();
}
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
!kickedFromCurrent, result);
handleKickEvent(originalEvent, friendlyReason);
result = Notify.create(friendlyReason);
}
KickedFromServerEvent originalEvent = new KickedFromServerEvent(this, rs, kickReason,
!kickedFromCurrent, result);
handleKickEvent(originalEvent, friendlyReason);
}
private void handleKickEvent(KickedFromServerEvent originalEvent, Component friendlyReason) {

Datei anzeigen

@ -1,24 +1,21 @@
package com.velocitypowered.proxy.connection.client;
import static com.google.common.net.UrlEscapers.urlFormParameterEscaper;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.proxy.VelocityServer.GSON;
import static com.velocitypowered.proxy.connection.VelocityConstants.EMPTY_BYTE_ARRAY;
import static com.velocitypowered.proxy.connection.VelocityConstants.VELOCITY_IP_FORWARDING_CHANNEL;
import static com.velocitypowered.proxy.util.EncryptionUtils.decryptRsa;
import static com.velocitypowered.proxy.util.EncryptionUtils.generateServerId;
import static org.asynchttpclient.Dsl.asyncHttpClient;
import static org.asynchttpclient.Dsl.config;
import com.google.common.base.Preconditions;
import com.google.common.net.UrlEscapers;
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.event.player.PlayerChooseInitialServerEvent;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.proxy.VelocityServer;
@ -29,17 +26,12 @@ import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.Disconnect;
import com.velocitypowered.proxy.protocol.packet.EncryptionRequest;
import com.velocitypowered.proxy.protocol.packet.EncryptionResponse;
import com.velocitypowered.proxy.protocol.packet.LoginPluginMessage;
import com.velocitypowered.proxy.protocol.packet.LoginPluginResponse;
import com.velocitypowered.proxy.protocol.packet.ServerLogin;
import com.velocitypowered.proxy.protocol.packet.ServerLoginSuccess;
import com.velocitypowered.proxy.protocol.packet.SetCompression;
import com.velocitypowered.proxy.util.VelocityMessages;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.URL;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.util.Arrays;
@ -50,7 +42,6 @@ import java.util.concurrent.ThreadLocalRandom;
import net.kyori.text.Component;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.asynchttpclient.Dsl;
import org.asynchttpclient.ListenableFuture;
import org.asynchttpclient.Response;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -66,7 +57,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
private final InitialInboundConnection inbound;
private @MonotonicNonNull ServerLogin login;
private byte[] verify = EMPTY_BYTE_ARRAY;
private int playerInfoId;
private @MonotonicNonNull ConnectedPlayer connectedPlayer;
LoginSessionHandler(VelocityServer server, MinecraftConnection mcConnection,
@ -79,29 +69,7 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
@Override
public boolean handle(ServerLogin packet) {
this.login = packet;
if (mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_13) >= 0) {
// To make sure the connecting client isn't Velocity, send a plugin message that Velocity will
// recognize and respond to.
playerInfoId = ThreadLocalRandom.current().nextInt();
mcConnection.write(new LoginPluginMessage(playerInfoId, VELOCITY_IP_FORWARDING_CHANNEL,
Unpooled.EMPTY_BUFFER));
} else {
beginPreLogin();
}
return true;
}
@Override
public boolean handle(LoginPluginResponse packet) {
if (packet.getId() == playerInfoId) {
if (packet.isSuccess()) {
// Uh oh, someone's trying to run Velocity behind Velocity. We don't want that happening.
inbound.disconnect(VelocityMessages.NO_PROXY_BEHIND_PROXY);
} else {
// Proceed with the regular login process.
beginPreLogin();
}
}
beginPreLogin();
return true;
}
@ -254,12 +222,6 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
}
private void finishLogin(ConnectedPlayer player) {
Optional<RegisteredServer> toTry = player.getNextServerToTry();
if (!toTry.isPresent()) {
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
return;
}
int threshold = server.getConfiguration().getCompressionThreshold();
if (threshold >= 0 && mcConnection.getProtocolVersion().compareTo(MINECRAFT_1_8) >= 0) {
mcConnection.write(new SetCompression(threshold));
@ -292,11 +254,27 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
mcConnection.setSessionHandler(new InitialConnectSessionHandler(player));
server.getEventManager().fire(new PostLoginEvent(player))
.thenRun(() -> player.createConnectionRequest(toTry.get()).fireAndForget());
.thenRun(() -> connectToInitialServer(player));
}
}, mcConnection.eventLoop());
}
private void connectToInitialServer(ConnectedPlayer player) {
Optional<RegisteredServer> initialFromConfig = player.getNextServerToTry();
PlayerChooseInitialServerEvent event = new PlayerChooseInitialServerEvent(player,
initialFromConfig.orElse(null));
server.getEventManager().fire(event)
.thenRunAsync(() -> {
Optional<RegisteredServer> toTry = event.getInitialServer();
if (!toTry.isPresent()) {
player.disconnect(VelocityMessages.NO_AVAILABLE_SERVERS);
return;
}
player.createConnectionRequest(toTry.get()).fireAndForget();
}, mcConnection.eventLoop());
}
@Override
public void handleUnknown(ByteBuf buf) {
mcConnection.close();

Datei anzeigen

@ -5,6 +5,7 @@ import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_12_1;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_13;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_14;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_15;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_7_2;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_8;
import static com.velocitypowered.api.network.ProtocolVersion.MINECRAFT_1_9;
@ -120,51 +121,61 @@ public enum StateRegistry {
map(0x1F, MINECRAFT_1_14, false));
clientbound.register(BossBar.class, BossBar::new,
map(0x0C, MINECRAFT_1_9, false));
map(0x0C, MINECRAFT_1_9, false),
map(0x0D, MINECRAFT_1_15, false));
clientbound.register(Chat.class, Chat::new,
map(0x02, MINECRAFT_1_7_2, true),
map(0x0F, MINECRAFT_1_9, true),
map(0x0E, MINECRAFT_1_13, true));
map(0x0E, MINECRAFT_1_13, true),
map(0x0F, MINECRAFT_1_15, true));
clientbound.register(TabCompleteResponse.class, TabCompleteResponse::new,
map(0x3A, MINECRAFT_1_7_2, false),
map(0x0E, MINECRAFT_1_9, false),
map(0x10, MINECRAFT_1_13, false));
map(0x10, MINECRAFT_1_13, false),
map(0x11, MINECRAFT_1_15, false));
clientbound.register(AvailableCommands.class, AvailableCommands::new,
map(0x11, MINECRAFT_1_13, false));
map(0x11, MINECRAFT_1_13, false),
map(0x12, MINECRAFT_1_15, false));
clientbound.register(PluginMessage.class, PluginMessage::new,
map(0x3F, MINECRAFT_1_7_2, false),
map(0x18, MINECRAFT_1_9, false),
map(0x19, MINECRAFT_1_13, false),
map(0x18, MINECRAFT_1_14, false));
map(0x18, MINECRAFT_1_14, false),
map(0x19, MINECRAFT_1_15, false));
clientbound.register(Disconnect.class, Disconnect::new,
map(0x40, MINECRAFT_1_7_2, false),
map(0x1A, MINECRAFT_1_9, false),
map(0x1B, MINECRAFT_1_13, false),
map(0x1A, MINECRAFT_1_14, false));
map(0x1A, MINECRAFT_1_14, false),
map(0x1B, MINECRAFT_1_15, false));
clientbound.register(KeepAlive.class, KeepAlive::new,
map(0x00, MINECRAFT_1_7_2, false),
map(0x1F, MINECRAFT_1_9, false),
map(0x21, MINECRAFT_1_13, false),
map(0x20, MINECRAFT_1_14, false));
map(0x20, MINECRAFT_1_14, false),
map(0x21, MINECRAFT_1_15, false));
clientbound.register(JoinGame.class, JoinGame::new,
map(0x01, MINECRAFT_1_7_2, false),
map(0x23, MINECRAFT_1_9, false),
map(0x25, MINECRAFT_1_13, false),
map(0x25, MINECRAFT_1_14, false));
map(0x25, MINECRAFT_1_14, false),
map(0x26, MINECRAFT_1_15, false));
clientbound.register(Respawn.class, Respawn::new,
map(0x07, MINECRAFT_1_7_2, true),
map(0x33, MINECRAFT_1_9, true),
map(0x34, MINECRAFT_1_12, true),
map(0x35, MINECRAFT_1_12_1, true),
map(0x38, MINECRAFT_1_13, true),
map(0x3A, MINECRAFT_1_14, true));
map(0x3A, MINECRAFT_1_14, true),
map(0x3B, MINECRAFT_1_15, true));
clientbound.register(ResourcePackRequest.class, ResourcePackRequest::new,
map(0x48, MINECRAFT_1_8, true),
map(0x32, MINECRAFT_1_9, true),
map(0x33, MINECRAFT_1_12, true),
map(0x34, MINECRAFT_1_12_1, true),
map(0x37, MINECRAFT_1_13, true),
map(0x39, MINECRAFT_1_14, true));
map(0x39, MINECRAFT_1_14, true),
map(0x3A, MINECRAFT_1_15, true));
clientbound.register(HeaderAndFooter.class, HeaderAndFooter::new,
map(0x47, MINECRAFT_1_8, true),
map(0x48, MINECRAFT_1_9, true),
@ -172,20 +183,23 @@ public enum StateRegistry {
map(0x49, MINECRAFT_1_12, true),
map(0x4A, MINECRAFT_1_12_1, true),
map(0x4E, MINECRAFT_1_13, true),
map(0x53, MINECRAFT_1_14, true));
map(0x53, MINECRAFT_1_14, true),
map(0x54, MINECRAFT_1_15, 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),
map(0x4F, MINECRAFT_1_14, true));
map(0x4F, MINECRAFT_1_14, true),
map(0x50, MINECRAFT_1_15, true));
clientbound.register(PlayerListItem.class, PlayerListItem::new,
map(0x38, MINECRAFT_1_7_2, false),
map(0x2D, MINECRAFT_1_9, false),
map(0x2E, MINECRAFT_1_12_1, false),
map(0x30, MINECRAFT_1_13, false),
map(0x33, MINECRAFT_1_14, false));
map(0x33, MINECRAFT_1_14, false),
map(0x34, MINECRAFT_1_15, false));
}
},
LOGIN {

Datei anzeigen

@ -18,6 +18,7 @@ import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.socket.DatagramPacket;
import java.net.InetAddress;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
@ -59,6 +60,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
private final Cache<InetAddress, Integer> sessions = CacheBuilder.newBuilder()
.expireAfterWrite(30, TimeUnit.SECONDS)
.build();
private final SecureRandom random;
private volatile @MonotonicNonNull List<QueryResponse.PluginInformation> pluginInformationList
= null;
@ -67,6 +69,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
public GS4QueryHandler(VelocityServer server) {
this.server = server;
this.random = new SecureRandom();
}
private QueryResponse createInitialResponse() {
@ -111,7 +114,7 @@ public class GS4QueryHandler extends SimpleChannelInboundHandler<DatagramPacket>
switch (type) {
case QUERY_TYPE_HANDSHAKE: {
// Generate new challenge token and put it into the sessions cache
int challengeToken = ThreadLocalRandom.current().nextInt();
int challengeToken = random.nextInt();
sessions.put(senderAddress, challengeToken);
// Respond with challenge token

Datei anzeigen

@ -12,11 +12,13 @@ public class JoinGame implements MinecraftPacket {
private int entityId;
private short gamemode;
private int dimension;
private long partialHashedSeed; // 1.15+
private short difficulty;
private short maxPlayers;
private @Nullable String levelType;
private int viewDistance; //1.14+
private boolean reducedDebugInfo;
private boolean mystery;
public int getEntityId() {
return entityId;
@ -42,6 +44,10 @@ public class JoinGame implements MinecraftPacket {
this.dimension = dimension;
}
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public short getDifficulty() {
return difficulty;
}
@ -91,6 +97,7 @@ public class JoinGame implements MinecraftPacket {
+ "entityId=" + entityId
+ ", gamemode=" + gamemode
+ ", dimension=" + dimension
+ ", partialHashedSeed=" + partialHashedSeed
+ ", difficulty=" + difficulty
+ ", maxPlayers=" + maxPlayers
+ ", levelType='" + levelType + '\''
@ -111,6 +118,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
this.difficulty = buf.readUnsignedByte();
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.partialHashedSeed = buf.readLong();
}
this.maxPlayers = buf.readUnsignedByte();
this.levelType = ProtocolUtils.readString(buf, 16);
if (version.compareTo(ProtocolVersion.MINECRAFT_1_14) >= 0) {
@ -119,6 +129,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
this.reducedDebugInfo = buf.readBoolean();
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.mystery = buf.readBoolean();
}
}
@Override
@ -133,6 +146,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
buf.writeByte(difficulty);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(maxPlayers);
if (levelType == null) {
throw new IllegalStateException("No level type specified.");
@ -144,6 +160,9 @@ public class JoinGame implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_8) >= 0) {
buf.writeBoolean(reducedDebugInfo);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeBoolean(mystery);
}
}
@Override

Datei anzeigen

@ -9,6 +9,7 @@ import io.netty.buffer.ByteBuf;
public class Respawn implements MinecraftPacket {
private int dimension;
private long partialHashedSeed;
private short difficulty;
private short gamemode;
private String levelType = "";
@ -16,8 +17,10 @@ public class Respawn implements MinecraftPacket {
public Respawn() {
}
public Respawn(int dimension, short difficulty, short gamemode, String levelType) {
public Respawn(int dimension, long partialHashedSeed, short difficulty, short gamemode,
String levelType) {
this.dimension = dimension;
this.partialHashedSeed = partialHashedSeed;
this.difficulty = difficulty;
this.gamemode = gamemode;
this.levelType = levelType;
@ -31,6 +34,14 @@ public class Respawn implements MinecraftPacket {
this.dimension = dimension;
}
public long getPartialHashedSeed() {
return partialHashedSeed;
}
public void setPartialHashedSeed(long partialHashedSeed) {
this.partialHashedSeed = partialHashedSeed;
}
public short getDifficulty() {
return difficulty;
}
@ -59,6 +70,7 @@ public class Respawn implements MinecraftPacket {
public String toString() {
return "Respawn{"
+ "dimension=" + dimension
+ ", partialHashedSeed=" + partialHashedSeed
+ ", difficulty=" + difficulty
+ ", gamemode=" + gamemode
+ ", levelType='" + levelType + '\''
@ -71,6 +83,9 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
this.difficulty = buf.readUnsignedByte();
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
this.partialHashedSeed = buf.readLong();
}
this.gamemode = buf.readUnsignedByte();
this.levelType = ProtocolUtils.readString(buf, 16);
}
@ -81,6 +96,9 @@ public class Respawn implements MinecraftPacket {
if (version.compareTo(ProtocolVersion.MINECRAFT_1_13_2) <= 0) {
buf.writeByte(difficulty);
}
if (version.compareTo(ProtocolVersion.MINECRAFT_1_15) >= 0) {
buf.writeLong(partialHashedSeed);
}
buf.writeByte(gamemode);
ProtocolUtils.writeString(buf, levelType);
}

Datei anzeigen

@ -0,0 +1,110 @@
package com.velocitypowered.proxy.protocol.util;
import com.google.common.io.ByteArrayDataInput;
import io.netty.buffer.ByteBuf;
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.IOException;
/**
* A wrapper around {@link io.netty.buffer.ByteBuf} that implements the exception-free
* {@link ByteArrayDataInput} interface from Guava.
*/
public class ByteBufDataInput implements ByteArrayDataInput, DataInput {
private final ByteBuf in;
/**
* Creates a new ByteBufDataInput instance. The ByteBufDataInput simply "borrows" the ByteBuf
* while it is in use.
*
* @param buf the buffer to read from
*/
public ByteBufDataInput(ByteBuf buf) {
this.in = buf;
}
public ByteBuf unwrap() {
return in;
}
@Override
public void readFully(byte[] b) {
in.readBytes(b);
}
@Override
public void readFully(byte[] b, int off, int len) {
in.readBytes(b, off, len);
}
@Override
public int skipBytes(int n) {
in.skipBytes(n);
return n;
}
@Override
public boolean readBoolean() {
return in.readBoolean();
}
@Override
public byte readByte() {
return in.readByte();
}
@Override
public int readUnsignedByte() {
return in.readUnsignedByte() & 0xFF;
}
@Override
public short readShort() {
return in.readShort();
}
@Override
public int readUnsignedShort() {
return in.readUnsignedShort();
}
@Override
public char readChar() {
return in.readChar();
}
@Override
public int readInt() {
return in.readInt();
}
@Override
public long readLong() {
return in.readLong();
}
@Override
public float readFloat() {
return in.readFloat();
}
@Override
public double readDouble() {
return in.readDouble();
}
@Override
public String readLine() {
throw new UnsupportedOperationException();
}
@Override
public String readUTF() {
try {
return DataInputStream.readUTF(this);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}

Datei anzeigen

@ -0,0 +1,105 @@
package com.velocitypowered.proxy.protocol.util;
import com.google.common.io.ByteArrayDataOutput;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufUtil;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.nio.charset.StandardCharsets;
/**
* A {@link DataOutput} equivalent to {@link ByteBufDataInput}.
*/
public class ByteBufDataOutput extends OutputStream implements DataOutput, ByteArrayDataOutput {
private final ByteBuf buf;
private final DataOutputStream utf8out;
public ByteBufDataOutput(ByteBuf buf) {
this.buf = buf;
this.utf8out = new DataOutputStream(this);
}
@Override
public byte[] toByteArray() {
return ByteBufUtil.getBytes(buf);
}
@Override
public void write(int b) {
buf.writeByte(b);
}
@Override
public void write(byte[] b) {
buf.writeBytes(b);
}
@Override
public void write(byte[] b, int off, int len) {
buf.writeBytes(b, off, len);
}
@Override
public void writeBoolean(boolean v) {
buf.writeBoolean(v);
}
@Override
public void writeByte(int v) {
buf.writeByte(v);
}
@Override
public void writeShort(int v) {
buf.writeShort(v);
}
@Override
public void writeChar(int v) {
buf.writeChar(v);
}
@Override
public void writeInt(int v) {
buf.writeInt(v);
}
@Override
public void writeLong(long v) {
buf.writeLong(v);
}
@Override
public void writeFloat(float v) {
buf.writeFloat(v);
}
@Override
public void writeDouble(double v) {
buf.writeDouble(v);
}
@Override
public void writeBytes(String s) {
buf.writeCharSequence(s, StandardCharsets.US_ASCII);
}
@Override
public void writeChars(String s) {
for (char c : s.toCharArray()) {
buf.writeChar(c);
}
}
@Override
public void writeUTF(String s) {
try {
this.utf8out.writeUTF(s);
} catch (IOException e) {
throw new IllegalStateException(e);
}
}
}

Datei anzeigen

@ -18,12 +18,16 @@ 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.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
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.packet.PluginMessage;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
@ -119,8 +123,18 @@ public class VelocityRegisteredServer implements RegisteredServer {
@Override
public boolean sendPluginMessage(ChannelIdentifier identifier, byte[] data) {
return sendPluginMessage(identifier, Unpooled.wrappedBuffer(data));
}
/**
* Sends a plugin message to the server through this connection.
* @param identifier the channel ID to use
* @param data the data
* @return whether or not the message was sent
*/
public boolean sendPluginMessage(ChannelIdentifier identifier, ByteBuf data) {
for (ConnectedPlayer player : players) {
ServerConnection connection = player.getConnectedServer();
VelocityServerConnection connection = player.getConnectedServer();
if (connection != null && connection.getServerInfo().equals(serverInfo)) {
return connection.sendPluginMessage(identifier, data);
}

Datei anzeigen

@ -0,0 +1,17 @@
package com.velocitypowered.proxy.util.except;
/**
* A special-purpose exception thrown when we want to indicate an error condition but do not want
* to see a large stack trace in logs.
*/
public class QuietException extends RuntimeException {
public QuietException(String message) {
super(message);
}
@Override
public synchronized Throwable fillInStackTrace() {
return this;
}
}