From 48fafc73be9657dd1e673611efb957077625945e Mon Sep 17 00:00:00 2001 From: Andrew Steinborn Date: Mon, 30 Jul 2018 01:03:42 -0400 Subject: [PATCH] Correctly handle boss bars on server transfers --- build.gradle | 1 + .../backend/BackendPlaySessionHandler.java | 22 ++- .../backend/LoginSessionHandler.java | 5 +- .../connection/backend/ServerConnection.java | 2 + .../client/ClientPlaySessionHandler.java | 24 ++- .../proxy/protocol/ProtocolUtils.java | 12 ++ .../proxy/protocol/StateRegistry.java | 2 + .../proxy/protocol/packets/BossBar.java | 147 ++++++++++++++++++ 8 files changed, 203 insertions(+), 12 deletions(-) create mode 100644 src/main/java/com/velocitypowered/proxy/protocol/packets/BossBar.java diff --git a/build.gradle b/build.gradle index 36f4f7ac6..95e10b793 100644 --- a/build.gradle +++ b/build.gradle @@ -43,6 +43,7 @@ dependencies { jar { manifest { attributes 'Main-Class': 'com.velocitypowered.proxy.Velocity' + attributes 'Implementation-Version': project.version } } diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index db342c2ad..1663fe226 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -2,11 +2,8 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.protocol.MinecraftPacket; -import com.velocitypowered.proxy.protocol.packets.Disconnect; -import com.velocitypowered.proxy.protocol.packets.JoinGame; -import com.velocitypowered.proxy.protocol.packets.KeepAlive; +import com.velocitypowered.proxy.protocol.packets.*; import com.velocitypowered.proxy.connection.MinecraftSessionHandler; -import com.velocitypowered.proxy.protocol.packets.Respawn; import io.netty.buffer.ByteBuf; public class BackendPlaySessionHandler implements MinecraftSessionHandler { @@ -18,6 +15,8 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public void handle(MinecraftPacket packet) { + ClientPlaySessionHandler playerHandler = + (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); if (packet instanceof KeepAlive) { // Forward onto the server connection.getChannel().write(packet); @@ -25,15 +24,22 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { Disconnect original = (Disconnect) packet; connection.getProxyPlayer().handleConnectionException(connection.getServerInfo(), original); } else if (packet instanceof JoinGame) { - ClientPlaySessionHandler playerHandler = - (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); playerHandler.handleBackendJoinGame((JoinGame) packet); } else if (packet instanceof Respawn) { // Record the dimension switch, and then forward the packet on. - ClientPlaySessionHandler playerHandler = - (ClientPlaySessionHandler) connection.getProxyPlayer().getConnection().getSessionHandler(); playerHandler.setCurrentDimension(((Respawn) packet).getDimension()); connection.getProxyPlayer().getConnection().write(packet); + } else if (packet instanceof BossBar) { + BossBar bossBar = (BossBar) packet; + switch (bossBar.getAction()) { + case 0: // add + playerHandler.getServerBossBars().add(bossBar.getUuid()); + break; + case 1: // remove + playerHandler.getServerBossBars().remove(bossBar.getUuid()); + break; + } + connection.getProxyPlayer().getConnection().write(packet); } else { // Just forward the packet on. We don't have anything to handle at this time. connection.getProxyPlayer().getConnection().write(packet); diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index b4e595aba..cf1b72639 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -36,12 +36,13 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (packet instanceof ServerLoginSuccess) { // The player has been logged on to the backend server. connection.getChannel().setState(StateRegistry.PLAY); - if (connection.getProxyPlayer().getConnectedServer() == null) { + ServerConnection existingConnection = connection.getProxyPlayer().getConnectedServer(); + if (existingConnection == null) { // Strap on the play session handler connection.getProxyPlayer().getConnection().setSessionHandler(new ClientPlaySessionHandler(connection.getProxyPlayer())); } else { // The previous server connection should become obsolete. - connection.getProxyPlayer().getConnectedServer().disconnect(); + existingConnection.disconnect(); } connection.getChannel().setSessionHandler(new BackendPlaySessionHandler(connection)); connection.getProxyPlayer().setConnectedServer(connection); diff --git a/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java b/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java index 3a5517948..6fb2ed13e 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java +++ b/src/main/java/com/velocitypowered/proxy/connection/backend/ServerConnection.java @@ -17,6 +17,8 @@ import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import io.netty.channel.*; import io.netty.handler.timeout.ReadTimeoutHandler; +import java.util.List; +import java.util.UUID; import java.util.concurrent.TimeUnit; import static com.velocitypowered.network.Connections.FRAME_DECODER; diff --git a/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index ef48336d8..4f1b2933e 100644 --- a/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -15,6 +15,9 @@ import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.net.InetSocketAddress; +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; import java.util.concurrent.ScheduledFuture; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.TimeUnit; @@ -26,6 +29,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { private ScheduledFuture pingTask; private long lastPing = -1; private boolean spawned = false; + private final List serverBossBars = new ArrayList<>(); private int currentDimension; public ClientPlaySessionHandler(ConnectedPlayer player) { @@ -105,7 +109,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // nothing special to do here spawned = true; currentDimension = joinGame.getDimension(); - player.getConnection().write(joinGame); + player.getConnection().delayedWrite(joinGame); } else { // In order to handle switching to another server we will need send three packets: // - The join game packet from the backend server @@ -117,16 +121,32 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { int tempDim = joinGame.getDimension() == 0 ? -1 : 0; player.getConnection().delayedWrite(new Respawn(tempDim, joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); player.getConnection().delayedWrite(new Respawn(joinGame.getDimension(), joinGame.getDifficulty(), joinGame.getGamemode(), joinGame.getLevelType())); - player.getConnection().flush(); currentDimension = joinGame.getDimension(); } + // Resend client settings packet to remote server if we have it, this preserves client settings across + // transitions. if (player.getClientSettings() != null) { player.getConnectedServer().getChannel().write(player.getClientSettings()); } + + // Remove old boss bars. + for (UUID serverBossBar : serverBossBars) { + BossBar deletePacket = new BossBar(); + deletePacket.setUuid(serverBossBar); + deletePacket.setAction(1); // remove + player.getConnection().delayedWrite(deletePacket); + } + + serverBossBars.clear(); + player.getConnection().flush(); } public void setCurrentDimension(int currentDimension) { this.currentDimension = currentDimension; } + + public List getServerBossBars() { + return serverBossBars; + } } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java b/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java index 1f68f3066..7d21cd649 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/ProtocolUtils.java @@ -4,6 +4,7 @@ import com.google.common.base.Preconditions; import io.netty.buffer.ByteBuf; import java.nio.charset.StandardCharsets; +import java.util.UUID; public enum ProtocolUtils { ; private static final int DEFAULT_MAX_STRING_SIZE = 65536; // 64KiB @@ -66,4 +67,15 @@ public enum ProtocolUtils { ; writeVarInt(buf, array.length); buf.writeBytes(array); } + + public static UUID readUuid(ByteBuf buf) { + long msb = buf.readLong(); + long lsb = buf.readLong(); + return new UUID(msb, lsb); + } + + public static void writeUuid(ByteBuf buf, UUID uuid) { + buf.writeLong(uuid.getMostSignificantBits()); + buf.writeLong(uuid.getLeastSignificantBits()); + } } diff --git a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java index d40f57a88..10a76cab5 100644 --- a/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java +++ b/src/main/java/com/velocitypowered/proxy/protocol/StateRegistry.java @@ -44,6 +44,8 @@ public enum StateRegistry { map(0x05, MINECRAFT_1_12), map(0x04, MINECRAFT_1_12_1)); + CLIENTBOUND.register(BossBar.class, BossBar::new, + map(0x0C, MINECRAFT_1_11)); CLIENTBOUND.register(Chat.class, Chat::new, map(0x0F, MINECRAFT_1_11)); CLIENTBOUND.register(Disconnect.class, Disconnect::new, diff --git a/src/main/java/com/velocitypowered/proxy/protocol/packets/BossBar.java b/src/main/java/com/velocitypowered/proxy/protocol/packets/BossBar.java new file mode 100644 index 000000000..7d61b71e3 --- /dev/null +++ b/src/main/java/com/velocitypowered/proxy/protocol/packets/BossBar.java @@ -0,0 +1,147 @@ +package com.velocitypowered.proxy.protocol.packets; + +import com.velocitypowered.proxy.protocol.MinecraftPacket; +import com.velocitypowered.proxy.protocol.ProtocolConstants; +import com.velocitypowered.proxy.protocol.ProtocolUtils; +import io.netty.buffer.ByteBuf; + +import java.util.UUID; + +public class BossBar implements MinecraftPacket { + private UUID uuid; + private int action; + private String title; + private float health; + private int color; + private int divisions; + private short flags; + + public UUID getUuid() { + return uuid; + } + + public void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public int getAction() { + return action; + } + + public void setAction(int action) { + this.action = action; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public float getHealth() { + return health; + } + + public void setHealth(float health) { + this.health = health; + } + + public int getColor() { + return color; + } + + public void setColor(int color) { + this.color = color; + } + + public int getDivisions() { + return divisions; + } + + public void setDivisions(int divisions) { + this.divisions = divisions; + } + + public short getFlags() { + return flags; + } + + public void setFlags(short flags) { + this.flags = flags; + } + + @Override + public String toString() { + return "BossBar{" + + "uuid=" + uuid + + ", action=" + action + + ", title='" + title + '\'' + + ", health=" + health + + ", color=" + color + + ", divisions=" + divisions + + ", flags=" + flags + + '}'; + } + + @Override + public void decode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + this.uuid = ProtocolUtils.readUuid(buf); + this.action = ProtocolUtils.readVarInt(buf); + switch (action) { + case 0: // add + this.title = ProtocolUtils.readString(buf); + this.health = buf.readFloat(); + this.color = ProtocolUtils.readVarInt(buf); + this.divisions = ProtocolUtils.readVarInt(buf); + this.flags = buf.readUnsignedByte(); + break; + case 1: // remove + break; + case 2: // set health + this.health = buf.readFloat(); + break; + case 3: // update title + this.title = ProtocolUtils.readString(buf); + break; + case 4: // update style + this.color = ProtocolUtils.readVarInt(buf); + this.divisions = ProtocolUtils.readVarInt(buf); + break; + case 5: + this.flags = buf.readUnsignedByte(); + break; + } + } + + @Override + public void encode(ByteBuf buf, ProtocolConstants.Direction direction, int protocolVersion) { + ProtocolUtils.writeUuid(buf, uuid); + ProtocolUtils.writeVarInt(buf, action); + switch (action) { + case 0: // add + ProtocolUtils.writeString(buf, title); + buf.writeFloat(health); + ProtocolUtils.writeVarInt(buf, color); + ProtocolUtils.writeVarInt(buf, divisions); + buf.writeByte(flags); + break; + case 1: // remove + break; + case 2: // set health + buf.writeFloat(health); + break; + case 3: // update title + ProtocolUtils.writeString(buf, title); + break; + case 4: // update style + ProtocolUtils.writeVarInt(buf, color); + ProtocolUtils.writeVarInt(buf, divisions); + break; + case 5: + buf.writeByte(flags); + break; + } + } +}