3
0
Mirror von https://github.com/PaperMC/Velocity.git synchronisiert 2024-09-29 06:30:16 +02:00

Add Configuration State API (#1261)

* Implement Configuration State events

* Implement PlayerFinishedConfigurationEvent

* Fixed PlayerFinishConfigurationEvent execution

* Apply suggestions

* Fix keep alive when blocking PlayerFinishConfigurationEvent

* Add ServerConnection to configuration events

* Separate PlayPacketQueueHandler to fix AutoReadHolderHandler

---------

Co-authored-by: Gero <gecam59@gmail.com>
Dieser Commit ist enthalten in:
Adrian 2024-06-16 10:56:09 -05:00 committet von GitHub
Ursprung 350ea7f31e
Commit e60e2063a8
Es konnte kein GPG-Schlüssel zu dieser Signatur gefunden werden
GPG-Schlüssel-ID: B5690EEEBB952194
23 geänderte Dateien mit 264 neuen und 90 gelöschten Zeilen

Datei anzeigen

@ -0,0 +1,27 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player.configuration;
import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import org.jetbrains.annotations.NotNull;
/**
* This event is executed when a player with version 1.20.2 or higher enters the configuration phase.
* <p>From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed,
* the {@linkplain Player#getProtocolState()} method is guaranteed
* to return {@link ProtocolState#CONFIGURATION}.</p>
*
* @param player The player that has entered the configuration phase.
* @param server The server that will now (re-)configure the player.
* @since 3.3.0
* @sinceMinecraft 1.20.2
*/
public record PlayerEnterConfigurationEvent(@NotNull Player player, ServerConnection server) {
}

Datei anzeigen

@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player.configuration;
import com.velocitypowered.api.event.annotation.AwaitingEvent;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import org.jetbrains.annotations.NotNull;
/**
* This event is executed when the player is about to finish the Configuration state.
* <p>Velocity will wait for this event to finish the configuration phase on the client.</p>
*
* @param player The player who is about to complete the configuration phase.
* @param server The server that is currently (re-)configuring the player.
* @since 3.3.0
* @sinceMinecraft 1.20.2
*/
@AwaitingEvent
public record PlayerFinishConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) {
}

Datei anzeigen

@ -0,0 +1,26 @@
/*
* Copyright (C) 2024 Velocity Contributors
*
* The Velocity API is licensed under the terms of the MIT License. For more details,
* reference the LICENSE file in the api top-level directory.
*/
package com.velocitypowered.api.event.player.configuration;
import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import org.jetbrains.annotations.NotNull;
/**
* Event executed when a player of version 1.20.2 or higher finishes the Configuration state.
* <p>From this moment on, the {@link Player#getProtocolState()} method
* will return {@link ProtocolState#PLAY}.</p>
*
* @param player The player who has completed the Configuration state
* @param server The server that has (re-)configured the player.
* @since 3.3.0
* @sinceMinecraft 1.20.2
*/
public record PlayerFinishedConfigurationEvent(@NotNull Player player, @NotNull ServerConnection server) {
}

Datei anzeigen

@ -45,7 +45,7 @@ import com.velocitypowered.proxy.command.builtin.ShutdownCommand;
import com.velocitypowered.proxy.command.builtin.VelocityCommand; import com.velocitypowered.proxy.command.builtin.VelocityCommand;
import com.velocitypowered.proxy.config.VelocityConfiguration; import com.velocitypowered.proxy.config.VelocityConfiguration;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.util.ServerListPingHandler; import com.velocitypowered.proxy.connection.util.ServerListPingHandler;
import com.velocitypowered.proxy.console.VelocityConsole; import com.velocitypowered.proxy.console.VelocityConsole;
import com.velocitypowered.proxy.crypto.EncryptionUtils; import com.velocitypowered.proxy.crypto.EncryptionUtils;

Datei anzeigen

@ -47,7 +47,8 @@ import com.velocitypowered.proxy.protocol.netty.MinecraftCompressorAndLengthEnco
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder; import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder; import com.velocitypowered.proxy.protocol.netty.MinecraftVarintLengthEncoder;
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueHandler; import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueInboundHandler;
import com.velocitypowered.proxy.protocol.netty.PlayPacketQueueOutboundHandler;
import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket; import com.velocitypowered.proxy.protocol.packet.SetCompressionPacket;
import com.velocitypowered.proxy.util.except.QuietDecoderException; import com.velocitypowered.proxy.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBuf;
@ -148,13 +149,11 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
return; return;
} }
if (msg instanceof MinecraftPacket) { if (msg instanceof MinecraftPacket pkt) {
MinecraftPacket pkt = (MinecraftPacket) msg;
if (!pkt.handle(activeSessionHandler)) { if (!pkt.handle(activeSessionHandler)) {
activeSessionHandler.handleGeneric((MinecraftPacket) msg); activeSessionHandler.handleGeneric((MinecraftPacket) msg);
} }
} else if (msg instanceof HAProxyMessage) { } else if (msg instanceof HAProxyMessage proxyMessage) {
HAProxyMessage proxyMessage = (HAProxyMessage) msg;
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(), this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
proxyMessage.sourcePort()); proxyMessage.sourcePort());
} else if (msg instanceof ByteBuf) { } else if (msg instanceof ByteBuf) {
@ -383,9 +382,14 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
if (state == StateRegistry.CONFIG) { if (state == StateRegistry.CONFIG) {
// Activate the play packet queue // Activate the play packet queue
addPlayPacketQueueHandler(); addPlayPacketQueueHandler();
} else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) { } else {
// Remove the queue // Remove the queue
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE); if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) != null) {
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_OUTBOUND);
}
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) != null) {
this.channel.pipeline().remove(Connections.PLAY_PACKET_QUEUE_INBOUND);
}
} }
} }
@ -393,10 +397,13 @@ public class MinecraftConnection extends ChannelInboundHandlerAdapter {
* Adds the play packet queue handler. * Adds the play packet queue handler.
*/ */
public void addPlayPacketQueueHandler() { public void addPlayPacketQueueHandler() {
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) { if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE, this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE_OUTBOUND,
new PlayPacketQueueHandler(this.protocolVersion, new PlayPacketQueueOutboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftEncoder.class).getDirection()));
channel.pipeline().get(MinecraftEncoder.class).getDirection())); }
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_INBOUND) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_DECODER, Connections.PLAY_PACKET_QUEUE_INBOUND,
new PlayPacketQueueInboundHandler(this.protocolVersion, channel.pipeline().get(MinecraftDecoder.class).getDirection()));
} }
} }

Datei anzeigen

@ -40,8 +40,8 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler; import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler; import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;

Datei anzeigen

@ -37,7 +37,6 @@ import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled; import io.netty.buffer.Unpooled;
import java.util.Optional; import java.util.Optional;
import java.util.StringJoiner; import java.util.StringJoiner;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.ComponentSerializer; import net.kyori.adventure.text.serializer.ComponentSerializer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
@ -143,7 +142,7 @@ public class BungeeCordMessageResponder {
out.writeUTF("PlayerList"); out.writeUTF("PlayerList");
out.writeUTF(info.getServerInfo().getName()); out.writeUTF(info.getServerInfo().getName());
StringJoiner joiner = new StringJoiner(", "); final StringJoiner joiner = new StringJoiner(", ");
for (Player online : info.getPlayersConnected()) { for (Player online : info.getPlayersConnected()) {
joiner.add(online.getUsername()); joiner.add(online.getUsername());
} }
@ -187,10 +186,9 @@ public class BungeeCordMessageResponder {
Component messageComponent = serializer.deserialize(message); Component messageComponent = serializer.deserialize(message);
if (target.equals("ALL")) { if (target.equals("ALL")) {
proxy.sendMessage(Identity.nil(), messageComponent); proxy.sendMessage(messageComponent);
} else { } else {
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(Identity.nil(), proxy.getPlayer(target).ifPresent(player -> player.sendMessage(messageComponent));
messageComponent));
} }
} }

Datei anzeigen

@ -29,7 +29,7 @@ import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler; import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
@ -132,7 +132,8 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(KeepAlivePacket packet) { public boolean handle(KeepAlivePacket packet) {
serverConn.ensureConnected().write(packet); serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime());
serverConn.getPlayer().getConnection().write(packet);
return true; return true;
} }
@ -193,30 +194,25 @@ public class ConfigSessionHandler implements MinecraftSessionHandler {
@Override @Override
public boolean handle(FinishedUpdatePacket packet) { public boolean handle(FinishedUpdatePacket packet) {
MinecraftConnection smc = serverConn.ensureConnected(); final MinecraftConnection smc = serverConn.ensureConnected();
ConnectedPlayer player = serverConn.getPlayer(); final ConnectedPlayer player = serverConn.getPlayer();
ClientConfigSessionHandler configHandler = final ClientConfigSessionHandler configHandler = (ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
(ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
smc.setAutoReading(false);
// Even when not auto reading messages are still decoded. Decode them with the correct state
smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY); smc.getChannel().pipeline().get(MinecraftDecoder.class).setState(StateRegistry.PLAY);
configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> { //noinspection DataFlowIssue
configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> {
smc.write(FinishedUpdatePacket.INSTANCE);
if (serverConn == player.getConnectedServer()) { if (serverConn == player.getConnectedServer()) {
smc.setActiveSessionHandler(StateRegistry.PLAY); smc.setActiveSessionHandler(StateRegistry.PLAY);
player.sendPlayerListHeaderAndFooter( player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter());
player.getPlayerListHeader(), player.getPlayerListFooter());
// The client cleared the tab list. TODO: Restore changes done via TabList API // The client cleared the tab list. TODO: Restore changes done via TabList API
player.getTabList().clearAllSilent(); player.getTabList().clearAllSilent();
} else { } else {
smc.setActiveSessionHandler(StateRegistry.PLAY, smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture));
new TransitionSessionHandler(server, serverConn, resultFuture));
} }
if (player.resourcePackHandler().getFirstAppliedPack() == null if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) {
&& resourcePackToApply != null) {
player.resourcePackHandler().queueResourcePack(resourcePackToApply); player.resourcePackHandler().queueResourcePack(resourcePackToApply);
} }
smc.setAutoReading(true);
}, smc.eventLoop()); }, smc.eventLoop());
return true; return true;
} }

Datei anzeigen

@ -166,12 +166,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler {
if (player.getClientSettingsPacket() != null) { if (player.getClientSettingsPacket() != null) {
smc.write(player.getClientSettingsPacket()); smc.write(player.getClientSettingsPacket());
} }
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler) { if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) {
smc.setAutoReading(false); smc.setAutoReading(false);
((ClientPlaySessionHandler) player.getConnection() clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop());
.getActiveSessionHandler()).doSwitch().thenAcceptAsync((unused) -> {
smc.setAutoReading(true);
}, smc.eventLoop());
} }
} }

Datei anzeigen

@ -19,6 +19,8 @@ package com.velocitypowered.proxy.connection.client;
import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent;
import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
@ -246,13 +248,11 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler {
smc.write(brandPacket); smc.write(brandPacket);
} }
player.getConnection().eventLoop().execute(() -> { server.getEventManager().fire(new PlayerFinishConfigurationEvent(player, serverConn)).thenAcceptAsync(event -> {
player.getConnection().write(FinishedUpdatePacket.INSTANCE); player.getConnection().write(FinishedUpdatePacket.INSTANCE);
player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY); player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
}); server.getEventManager().fireAndForget(new PlayerFinishedConfigurationEvent(player, serverConn));
}, player.getConnection().eventLoop());
smc.write(FinishedUpdatePacket.INSTANCE);
smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
return configSwitchFuture; return configSwitchFuture;
} }

Datei anzeigen

@ -27,6 +27,7 @@ import com.velocitypowered.api.event.player.CookieReceiveEvent;
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent; import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
import com.velocitypowered.api.event.player.TabCompleteEvent; import com.velocitypowered.api.event.player.TabCompleteEvent;
import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.messages.ChannelIdentifier; import com.velocitypowered.api.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier; import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
@ -406,6 +407,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
// Complete client switch // Complete client switch
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG); player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
VelocityServerConnection serverConnection = player.getConnectedServer(); VelocityServerConnection serverConnection = player.getConnectedServer();
server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(player, serverConnection));
if (serverConnection != null) { if (serverConnection != null) {
MinecraftConnection smc = serverConnection.ensureConnected(); MinecraftConnection smc = serverConnection.ensureConnected();
CompletableFuture.runAsync(() -> { CompletableFuture.runAsync(() -> {
@ -512,7 +514,7 @@ public class ClientPlaySessionHandler implements MinecraftSessionHandler {
* @return a future that completes when the switch is complete * @return a future that completes when the switch is complete
*/ */
public CompletableFuture<Void> doSwitch() { public CompletableFuture<Void> doSwitch() {
VelocityServerConnection existingConnection = player.getConnectedServer(); final VelocityServerConnection existingConnection = player.getConnectedServer();
if (existingConnection != null) { if (existingConnection != null) {
// Shut down the existing server connection. // Shut down the existing server connection.

Datei anzeigen

@ -37,6 +37,7 @@ import com.velocitypowered.api.event.player.KickedFromServerEvent.ServerKickResu
import com.velocitypowered.api.event.player.PlayerModInfoEvent; import com.velocitypowered.api.event.player.PlayerModInfoEvent;
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent; import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
import com.velocitypowered.api.event.player.ServerPreConnectEvent; import com.velocitypowered.api.event.player.ServerPreConnectEvent;
import com.velocitypowered.api.event.player.configuration.PlayerEnterConfigurationEvent;
import com.velocitypowered.api.network.ProtocolState; import com.velocitypowered.api.network.ProtocolState;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.permission.PermissionFunction; import com.velocitypowered.api.permission.PermissionFunction;
@ -59,8 +60,9 @@ import com.velocitypowered.proxy.adventure.VelocityBossBarImplementation;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation; import com.velocitypowered.proxy.connection.MinecraftConnectionAssociation;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.bundle.BundleDelimiterHandler;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.handler.ResourcePackHandler;
import com.velocitypowered.proxy.connection.util.ConnectionMessages; import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl; import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection; import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
@ -806,7 +808,7 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
}, connection.eventLoop()); }, connection.eventLoop());
} else if (event.getResult() instanceof final Notify res) { } else if (event.getResult() instanceof final Notify res) {
if (event.kickedDuringServerConnect() && previousConnection != null) { if (event.kickedDuringServerConnect() && previousConnection != null) {
sendMessage(Identity.nil(), res.getMessageComponent()); sendMessage(res.getMessageComponent());
} else { } else {
disconnect(res.getMessageComponent()); disconnect(res.getMessageComponent());
} }
@ -1227,8 +1229,9 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
.get(MinecraftEncoder.class).setState(StateRegistry.CONFIG); .get(MinecraftEncoder.class).setState(StateRegistry.CONFIG);
// Make sure we don't send any play packets to the player after update start // Make sure we don't send any play packets to the player after update start
connection.addPlayPacketQueueHandler(); connection.addPlayPacketQueueHandler();
server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(this, connectionInFlight));
}, connection.eventLoop()).exceptionally((ex) -> { }, connection.eventLoop()).exceptionally((ex) -> {
logger.error("Error switching player connection to config state:", ex); logger.error("Error switching player connection to config state", ex);
return null; return null;
}); });
} }
@ -1363,24 +1366,20 @@ public class ConnectedPlayer implements MinecraftConnectionAssociation, Player,
} }
switch (status.getStatus()) { switch (status.getStatus()) {
case ALREADY_CONNECTED: case ALREADY_CONNECTED -> sendMessage(ConnectionMessages.ALREADY_CONNECTED);
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED); case CONNECTION_IN_PROGRESS -> sendMessage(ConnectionMessages.IN_PROGRESS);
break; case CONNECTION_CANCELLED -> {
case CONNECTION_IN_PROGRESS:
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
break;
case CONNECTION_CANCELLED:
// Ignored; the plugin probably already handled this. // Ignored; the plugin probably already handled this.
break; }
case SERVER_DISCONNECTED: case SERVER_DISCONNECTED -> {
Component reason = status.getReasonComponent() final Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR); .orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(toConnect, handleConnectionException(toConnect,
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe()); DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe());
break; }
default: default -> {
// The only remaining value is successful (no need to do anything!) // The only remaining value is successful (no need to do anything!)
break; }
} }
}, connection.eventLoop()).thenApply(Result::isSuccessful); }, connection.eventLoop()).thenApply(Result::isSuccessful);
} }

Datei anzeigen

@ -15,10 +15,11 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.connection.client; package com.velocitypowered.proxy.connection.player.bundle;
import com.velocitypowered.proxy.connection.MinecraftConnection; import com.velocitypowered.proxy.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.protocol.StateRegistry; import com.velocitypowered.proxy.protocol.StateRegistry;
import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket; import com.velocitypowered.proxy.protocol.packet.BundleDelimiterPacket;
import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletableFuture;

Datei anzeigen

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.connection.player; package com.velocitypowered.proxy.connection.player.resourcepack;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;

Datei anzeigen

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.connection.player.resourcepack; package com.velocitypowered.proxy.connection.player.resourcepack.handler;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;

Datei anzeigen

@ -15,13 +15,14 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.connection.player.resourcepack; package com.velocitypowered.proxy.connection.player.resourcepack.handler;
import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent; import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import java.util.ArrayDeque; import java.util.ArrayDeque;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;

Datei anzeigen

@ -15,7 +15,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.connection.player.resourcepack; package com.velocitypowered.proxy.connection.player.resourcepack.handler;
import com.google.common.collect.ListMultimap; import com.google.common.collect.ListMultimap;
import com.google.common.collect.Multimaps; import com.google.common.collect.Multimaps;
@ -23,6 +23,7 @@ import com.velocitypowered.api.event.player.PlayerResourcePackStatusEvent;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.LinkedList; import java.util.LinkedList;

Datei anzeigen

@ -15,14 +15,15 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>. * along with this program. If not, see <https://www.gnu.org/licenses/>.
*/ */
package com.velocitypowered.proxy.connection.player.resourcepack; package com.velocitypowered.proxy.connection.player.resourcepack.handler;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.VelocityServer; import com.velocitypowered.proxy.VelocityServer;
import com.velocitypowered.proxy.connection.backend.VelocityServerConnection; import com.velocitypowered.proxy.connection.backend.VelocityServerConnection;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer; import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackResponseBundle;
import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackRequestPacket;
import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket; import com.velocitypowered.proxy.protocol.packet.ResourcePackResponsePacket;
import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder; import com.velocitypowered.proxy.protocol.packet.chat.ComponentHolder;

Datei anzeigen

@ -35,7 +35,8 @@ public class Connections {
public static final String MINECRAFT_DECODER = "minecraft-decoder"; public static final String MINECRAFT_DECODER = "minecraft-decoder";
public static final String MINECRAFT_ENCODER = "minecraft-encoder"; public static final String MINECRAFT_ENCODER = "minecraft-encoder";
public static final String READ_TIMEOUT = "read-timeout"; public static final String READ_TIMEOUT = "read-timeout";
public static final String PLAY_PACKET_QUEUE = "play-packet-queue"; public static final String PLAY_PACKET_QUEUE_OUTBOUND = "play-packet-queue-outbound";
public static final String PLAY_PACKET_QUEUE_INBOUND = "play-packet-queue-inbound";
private Connections() { private Connections() {
throw new AssertionError(); throw new AssertionError();

Datei anzeigen

@ -56,8 +56,7 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
@Override @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) { if (msg instanceof ByteBuf buf) {
ByteBuf buf = (ByteBuf) msg;
tryDecode(ctx, buf); tryDecode(ctx, buf);
} else { } else {
ctx.fireChannelRead(msg); ctx.fireChannelRead(msg);
@ -147,4 +146,8 @@ public class MinecraftDecoder extends ChannelInboundHandlerAdapter {
this.state = state; this.state = state;
this.setProtocolVersion(registry.version); this.setProtocolVersion(registry.version);
} }
public ProtocolUtils.Direction getDirection() {
return direction;
}
} }

Datei anzeigen

@ -0,0 +1,94 @@
/*
* Copyright (C) 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 <https://www.gnu.org/licenses/>.
*/
package com.velocitypowered.proxy.protocol.netty;
import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.StateRegistry;
import io.netty.channel.ChannelDuplexHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.util.ReferenceCountUtil;
import io.netty.util.internal.PlatformDependent;
import java.util.Queue;
import org.jetbrains.annotations.NotNull;
/**
* Queues up any pending PLAY packets while the client is in the CONFIG state.
*
* <p>Much of the Velocity API (i.e. chat messages) utilize PLAY packets, however the client is
* incapable of receiving these packets during the CONFIG state. Certain events such as the
* ServerPreConnectEvent may be called during this time, and we need to ensure that any API that
* uses these packets will work as expected.
*
* <p>This handler will queue up any packets that are sent to the client during this time, and send
* them once the client has (re)entered the PLAY state.
*/
public class PlayPacketQueueInboundHandler extends ChannelDuplexHandler {
private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private final Queue<Object> queue = PlatformDependent.newMpscQueue();
/**
* Provides registries for client &amp; server bound packets.
*
* @param version the protocol version
*/
public PlayPacketQueueInboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof final MinecraftPacket packet) {
// If the packet exists in the CONFIG state, we want to always
// ensure that it gets handled by the current handler
if (this.registry.containsPacket(packet)) {
ctx.fireChannelRead(msg);
return;
}
}
// Otherwise, queue the packet
this.queue.offer(msg);
}
@Override
public void channelInactive(@NotNull ChannelHandlerContext ctx) throws Exception {
this.releaseQueue(ctx, false);
super.channelInactive(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
this.releaseQueue(ctx, ctx.channel().isActive());
}
private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
// Handle all the queued packets
Object msg;
while ((msg = this.queue.poll()) != null) {
if (active) {
ctx.fireChannelRead(msg);
} else {
ReferenceCountUtil.release(msg);
}
}
}
}

Datei anzeigen

@ -40,7 +40,7 @@ import org.jetbrains.annotations.NotNull;
* <p>This handler will queue up any packets that are sent to the client during this time, and send * <p>This handler will queue up any packets that are sent to the client during this time, and send
* them once the client has (re)entered the PLAY state. * them once the client has (re)entered the PLAY state.
*/ */
public class PlayPacketQueueHandler extends ChannelDuplexHandler { public class PlayPacketQueueOutboundHandler extends ChannelDuplexHandler {
private final StateRegistry.PacketRegistry.ProtocolRegistry registry; private final StateRegistry.PacketRegistry.ProtocolRegistry registry;
private final Queue<MinecraftPacket> queue = PlatformDependent.newMpscQueue(); private final Queue<MinecraftPacket> queue = PlatformDependent.newMpscQueue();
@ -50,28 +50,26 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler {
* *
* @param version the protocol version * @param version the protocol version
*/ */
public PlayPacketQueueHandler(ProtocolVersion version, ProtocolUtils.Direction direction) { public PlayPacketQueueOutboundHandler(ProtocolVersion version, ProtocolUtils.Direction direction) {
this.registry = this.registry = StateRegistry.CONFIG.getProtocolRegistry(direction, version);
StateRegistry.CONFIG.getProtocolRegistry(direction, version);
} }
@Override @Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
throws Exception { if (!(msg instanceof final MinecraftPacket packet)) {
if (!(msg instanceof MinecraftPacket)) {
ctx.write(msg, promise); ctx.write(msg, promise);
return; return;
} }
// If the packet exists in the CONFIG state, we want to always // If the packet exists in the CONFIG state, we want to always
// ensure that it gets sent out to the client // ensure that it gets sent out to the client
if (this.registry.containsPacket(((MinecraftPacket) msg))) { if (this.registry.containsPacket(packet)) {
ctx.write(msg, promise); ctx.write(msg, promise);
return; return;
} }
// Otherwise, queue the packet // Otherwise, queue the packet
this.queue.offer((MinecraftPacket) msg); this.queue.offer(packet);
} }
@Override @Override
@ -87,10 +85,6 @@ public class PlayPacketQueueHandler extends ChannelDuplexHandler {
} }
private void releaseQueue(ChannelHandlerContext ctx, boolean active) { private void releaseQueue(ChannelHandlerContext ctx, boolean active) {
if (this.queue.isEmpty()) {
return;
}
// Send out all the queued packets // Send out all the queued packets
MinecraftPacket packet; MinecraftPacket packet;
while ((packet = this.queue.poll()) != null) { while ((packet = this.queue.poll()) != null) {

Datei anzeigen

@ -21,7 +21,7 @@ import com.google.common.base.Preconditions;
import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.network.ProtocolVersion;
import com.velocitypowered.api.proxy.player.ResourcePackInfo; import com.velocitypowered.api.proxy.player.ResourcePackInfo;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler; import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo; import com.velocitypowered.proxy.connection.player.resourcepack.VelocityResourcePackInfo;
import com.velocitypowered.proxy.protocol.MinecraftPacket; import com.velocitypowered.proxy.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.ProtocolUtils; import com.velocitypowered.proxy.protocol.ProtocolUtils;
import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction; import com.velocitypowered.proxy.protocol.ProtocolUtils.Direction;