diff --git a/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java b/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java index 9dec3309..e2451188 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/adventure/VelocityBossBarImplementation.java @@ -53,15 +53,23 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, viewer.getProtocolVersion(), viewer.translateMessage(this.bar.name()) ); - viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name)); + viewer.getBossBarManager().writeUpdate(this, BossBarPacket.createAddPacket(this.id, this.bar, name)); return true; } return false; } + public void createDirect(final ConnectedPlayer viewer) { + final ComponentHolder name = new ComponentHolder( + viewer.getProtocolVersion(), + viewer.translateMessage(this.bar.name()) + ); + viewer.getConnection().write(BossBarPacket.createAddPacket(this.id, this.bar, name)); + } + public boolean viewerRemove(final ConnectedPlayer viewer) { if (this.viewers.remove(viewer)) { - viewer.getConnection().write(BossBarPacket.createRemovePacket(this.id, this.bar)); + viewer.getBossBarManager().remove(this, BossBarPacket.createRemovePacket(this.id, this.bar)); return true; } return false; @@ -84,7 +92,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, this.bar, new ComponentHolder(viewer.getProtocolVersion(), translated) ); - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -96,7 +104,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdateProgressPacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -108,7 +116,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -120,7 +128,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdateStylePacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } @@ -132,7 +140,7 @@ public final class VelocityBossBarImplementation implements BossBar.Listener, ) { final BossBarPacket packet = BossBarPacket.createUpdatePropertiesPacket(this.id, this.bar); for (final ConnectedPlayer viewer : this.viewers) { - viewer.getConnection().write(packet); + viewer.getBossBarManager().writeUpdate(this, packet); } } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java index eb9c9bca..ed30057a 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/BackendPlaySessionHandler.java @@ -177,10 +177,12 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { @Override public boolean handle(BossBarPacket packet) { - if (packet.getAction() == BossBarPacket.ADD) { - playerSessionHandler.getServerBossBars().add(packet.getUuid()); - } else if (packet.getAction() == BossBarPacket.REMOVE) { - playerSessionHandler.getServerBossBars().remove(packet.getUuid()); + if (serverConn.getPlayer().getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + if (packet.getAction() == BossBarPacket.ADD) { + playerSessionHandler.getServerBossBars().add(packet.getUuid()); + } else if (packet.getAction() == BossBarPacket.REMOVE) { + playerSessionHandler.getServerBossBars().remove(packet.getUuid()); + } } return false; // forward } @@ -516,4 +518,4 @@ public class BackendPlaySessionHandler implements MinecraftSessionHandler { playerConnection.setAutoReading(writable); } -} \ No newline at end of file +} diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java index 288315d7..17eb708b 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientPlaySessionHandler.java @@ -535,9 +535,13 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { // Config state clears everything in the client. No need to clear later. spawned = false; - serverBossBars.clear(); player.clearPlayerListHeaderAndFooterSilent(); player.getTabList().clearAllSilent(); + if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + player.getBossBarManager().dropPackets(); + } else { + serverBossBars.clear(); + } } player.switchToConfigState(); @@ -576,16 +580,19 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler { } destination.setEntityId(joinGame.getEntityId()); // used for sound api - - // Remove previous boss bars. These don't get cleared when sending JoinGame, thus the need to - // track them. - for (UUID serverBossBar : serverBossBars) { - BossBarPacket deletePacket = new BossBarPacket(); - deletePacket.setUuid(serverBossBar); - deletePacket.setAction(BossBarPacket.REMOVE); - player.getConnection().delayedWrite(deletePacket); + if (player.getProtocolVersion().noLessThan(ProtocolVersion.MINECRAFT_1_20_2)) { + player.getBossBarManager().sendBossBars(); + } else { + // Remove previous boss bars. These don't get cleared when sending JoinGame (up until 1.20.2), + // thus the need to track them. + for (UUID serverBossBar : serverBossBars) { + BossBarPacket deletePacket = new BossBarPacket(); + deletePacket.setUuid(serverBossBar); + deletePacket.setAction(BossBarPacket.REMOVE); + player.getConnection().delayedWrite(deletePacket); + } + serverBossBars.clear(); } - serverBossBars.clear(); // Tell the server about the proxy's plugin message channels. ProtocolVersion serverVersion = serverMc.getProtocolVersion(); diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java index 44fe95c5..17f4fb5c 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ConnectedPlayer.java @@ -62,6 +62,7 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; +import com.velocitypowered.proxy.connection.player.bossbar.BossBarManager; import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler; @@ -201,6 +202,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, private @Nullable ClientSettingsPacket clientSettingsPacket; private volatile ChatQueue chatQueue; private final ChatBuilderFactory chatBuilderFactory; + private final BossBarManager bossBarManager; ConnectedPlayer(VelocityServer server, GameProfile profile, MinecraftConnection connection, @Nullable InetSocketAddress virtualHost, @Nullable String rawVirtualHost, boolean onlineMode, @@ -227,6 +229,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, this.chatQueue = new ChatQueue(this); this.chatBuilderFactory = new ChatBuilderFactory(this.getProtocolVersion()); this.resourcePackHandler = ResourcePackHandler.create(this, server); + this.bossBarManager = new BossBarManager(this); } /** @@ -1431,6 +1434,10 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player, return handshakeIntent; } + public BossBarManager getBossBarManager() { + return bossBarManager; + } + private final class ConnectionRequestBuilderImpl implements ConnectionRequestBuilder { private final RegisteredServer toConnect; diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bossbar/BossBarManager.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bossbar/BossBarManager.java new file mode 100644 index 00000000..03ff4f7f --- /dev/null +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/player/bossbar/BossBarManager.java @@ -0,0 +1,79 @@ +/* + * Copyright (C) 2019-2023 Velocity Contributors + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package com.velocitypowered.proxy.connection.player.bossbar; + +import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation; +import com.velocitypowered.proxy.connection.client.ConnectedPlayer; +import com.velocitypowered.proxy.protocol.packet.BossBarPacket; +import java.util.HashSet; +import java.util.Set; + +/** + * Handles dropping and resending boss bar packets on versions 1.20.2 and newer because the client now + * deletes all boss bars during the login phase, and sending update packets would cause the client to be disconnected. + */ +public final class BossBarManager { + + private final ConnectedPlayer player; + private final Set bossBars = new HashSet<>(); + + private boolean dropPackets = false; + + public BossBarManager(ConnectedPlayer player) { + this.player = player; + } + + /** + * Records the specified boss bar to be re-sent when a player changes server, and sends the update packet + * if the client is able to receive it and not be disconnected. + */ + public synchronized void writeUpdate(VelocityBossBarImplementation bar, BossBarPacket packet) { + this.bossBars.add(bar); + if (!this.dropPackets) { + this.player.getConnection().write(packet); + } + } + + /** + * Removes the specified boss bar from the player to ensure it is not re-sent. + */ + public synchronized void remove(VelocityBossBarImplementation bar, BossBarPacket packet) { + this.bossBars.remove(bar); + if (!this.dropPackets) { + this.player.getConnection().write(packet); + } + } + + /** + * Re-creates the boss bars the player can see with any updates that may have occurred in the meantime, + * and allows update packets for those boss bars to be sent. + */ + public synchronized void sendBossBars() { + for (VelocityBossBarImplementation bossBar : bossBars) { + bossBar.createDirect(player); + } + this.dropPackets = false; + } + + /** + * Prevents the player from receiving boss bar update packets while logging in to a new server. + */ + public synchronized void dropPackets() { + this.dropPackets = true; + } +}