Skip to content
This repository has been archived by the owner on Feb 1, 2025. It is now read-only.

Commit

Permalink
Add Configuration State API (PaperMC#1261)
Browse files Browse the repository at this point in the history
* 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 <[email protected]>
  • Loading branch information
2 people authored and qyl27 committed Jul 6, 2024
1 parent 15df134 commit 220352d
Show file tree
Hide file tree
Showing 23 changed files with 264 additions and 90 deletions.
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
@@ -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) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@
import com.velocitypowered.proxy.command.builtin.VelocityCommand;
import com.velocitypowered.proxy.config.VelocityConfiguration;
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.console.VelocityConsole;
import com.velocitypowered.proxy.crypto.EncryptionUtils;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
import com.velocitypowered.proxy.protocol.netty.MinecraftDecoder;
import com.velocitypowered.proxy.protocol.netty.MinecraftEncoder;
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.util.except.QuietDecoderException;
import io.netty.buffer.ByteBuf;
Expand Down Expand Up @@ -148,13 +149,11 @@ public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception
return;
}

if (msg instanceof MinecraftPacket) {
MinecraftPacket pkt = (MinecraftPacket) msg;
if (msg instanceof MinecraftPacket pkt) {
if (!pkt.handle(activeSessionHandler)) {
activeSessionHandler.handleGeneric((MinecraftPacket) msg);
}
} else if (msg instanceof HAProxyMessage) {
HAProxyMessage proxyMessage = (HAProxyMessage) msg;
} else if (msg instanceof HAProxyMessage proxyMessage) {
this.remoteAddress = new InetSocketAddress(proxyMessage.sourceAddress(),
proxyMessage.sourcePort());
} else if (msg instanceof ByteBuf) {
Expand Down Expand Up @@ -383,20 +382,28 @@ public void setState(StateRegistry state) {
if (state == StateRegistry.CONFIG) {
// Activate the play packet queue
addPlayPacketQueueHandler();
} else if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) != null) {
} else {
// 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);
}
}
}

/**
* Adds the play packet queue handler.
*/
public void addPlayPacketQueueHandler() {
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE,
new PlayPacketQueueHandler(this.protocolVersion,
channel.pipeline().get(MinecraftEncoder.class).getDirection()));
if (this.channel.pipeline().get(Connections.PLAY_PACKET_QUEUE_OUTBOUND) == null) {
this.channel.pipeline().addAfter(Connections.MINECRAFT_ENCODER, Connections.PLAY_PACKET_QUEUE_OUTBOUND,
new PlayPacketQueueOutboundHandler(this.protocolVersion, 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()));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientPlaySessionHandler;
import com.velocitypowered.proxy.connection.client.ConnectedPlayer;
import com.velocitypowered.proxy.connection.player.VelocityResourcePackInfo;
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.protocol.MinecraftPacket;
import com.velocitypowered.proxy.protocol.StateRegistry;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@
import io.netty.buffer.Unpooled;
import java.util.Optional;
import java.util.StringJoiner;
import net.kyori.adventure.identity.Identity;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.serializer.ComponentSerializer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
Expand Down Expand Up @@ -143,7 +142,7 @@ private void processPlayerList(ByteBufDataInput in) {
out.writeUTF("PlayerList");
out.writeUTF(info.getServerInfo().getName());

StringJoiner joiner = new StringJoiner(", ");
final StringJoiner joiner = new StringJoiner(", ");
for (Player online : info.getPlayersConnected()) {
joiner.add(online.getUsername());
}
Expand Down Expand Up @@ -187,10 +186,9 @@ private void processMessage0(ByteBufDataInput in,

Component messageComponent = serializer.deserialize(message);
if (target.equals("ALL")) {
proxy.sendMessage(Identity.nil(), messageComponent);
proxy.sendMessage(messageComponent);
} else {
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(Identity.nil(),
messageComponent));
proxy.getPlayer(target).ifPresent(player -> player.sendMessage(messageComponent));
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
import com.velocitypowered.proxy.connection.client.ClientConfigSessionHandler;
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.ConnectionRequestResults;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
Expand Down Expand Up @@ -132,7 +132,8 @@ public boolean handle(ClientboundServerLinksPacket packet) {

@Override
public boolean handle(KeepAlivePacket packet) {
serverConn.ensureConnected().write(packet);
serverConn.getPendingPings().put(packet.getRandomId(), System.nanoTime());
serverConn.getPlayer().getConnection().write(packet);
return true;
}

Expand Down Expand Up @@ -193,30 +194,25 @@ public boolean handle(final ResourcePackRequestPacket packet) {

@Override
public boolean handle(FinishedUpdatePacket packet) {
MinecraftConnection smc = serverConn.ensureConnected();
ConnectedPlayer player = serverConn.getPlayer();
ClientConfigSessionHandler configHandler =
(ClientConfigSessionHandler) player.getConnection().getActiveSessionHandler();
final MinecraftConnection smc = serverConn.ensureConnected();
final ConnectedPlayer player = serverConn.getPlayer();
final ClientConfigSessionHandler configHandler = (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);
configHandler.handleBackendFinishUpdate(serverConn).thenAcceptAsync((unused) -> {
//noinspection DataFlowIssue
configHandler.handleBackendFinishUpdate(serverConn).thenRunAsync(() -> {
smc.write(FinishedUpdatePacket.INSTANCE);
if (serverConn == player.getConnectedServer()) {
smc.setActiveSessionHandler(StateRegistry.PLAY);
player.sendPlayerListHeaderAndFooter(
player.getPlayerListHeader(), player.getPlayerListFooter());
player.sendPlayerListHeaderAndFooter(player.getPlayerListHeader(), player.getPlayerListFooter());
// The client cleared the tab list. TODO: Restore changes done via TabList API
player.getTabList().clearAllSilent();
} else {
smc.setActiveSessionHandler(StateRegistry.PLAY,
new TransitionSessionHandler(server, serverConn, resultFuture));
smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture));
}
if (player.resourcePackHandler().getFirstAppliedPack() == null
&& resourcePackToApply != null) {
if (player.resourcePackHandler().getFirstAppliedPack() == null && resourcePackToApply != null) {
player.resourcePackHandler().queueResourcePack(resourcePackToApply);
}
smc.setAutoReading(true);
}, smc.eventLoop());
return true;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -166,12 +166,9 @@ public boolean handle(ServerLoginSuccessPacket packet) {
if (player.getClientSettingsPacket() != null) {
smc.write(player.getClientSettingsPacket());
}
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler) {
if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) {
smc.setAutoReading(false);
((ClientPlaySessionHandler) player.getConnection()
.getActiveSessionHandler()).doSwitch().thenAcceptAsync((unused) -> {
smc.setAutoReading(true);
}, smc.eventLoop());
clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop());
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

import com.velocitypowered.api.event.player.CookieReceiveEvent;
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.connection.MinecraftConnection;
import com.velocitypowered.proxy.connection.MinecraftSessionHandler;
Expand Down Expand Up @@ -246,13 +248,11 @@ public CompletableFuture<Void> handleBackendFinishUpdate(VelocityServerConnectio
smc.write(brandPacket);
}

player.getConnection().eventLoop().execute(() -> {
server.getEventManager().fire(new PlayerFinishConfigurationEvent(player, serverConn)).thenAcceptAsync(event -> {
player.getConnection().write(FinishedUpdatePacket.INSTANCE);
player.getConnection().getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
});

smc.write(FinishedUpdatePacket.INSTANCE);
smc.getChannel().pipeline().get(MinecraftEncoder.class).setState(StateRegistry.PLAY);
server.getEventManager().fireAndForget(new PlayerFinishedConfigurationEvent(player, serverConn));
}, player.getConnection().eventLoop());

return configSwitchFuture;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
import com.velocitypowered.api.event.player.PlayerChannelRegisterEvent;
import com.velocitypowered.api.event.player.PlayerClientBrandEvent;
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.proxy.messages.ChannelIdentifier;
import com.velocitypowered.api.proxy.messages.LegacyChannelIdentifier;
Expand Down Expand Up @@ -406,6 +407,7 @@ public boolean handle(FinishedUpdatePacket packet) {
// Complete client switch
player.getConnection().setActiveSessionHandler(StateRegistry.CONFIG);
VelocityServerConnection serverConnection = player.getConnectedServer();
server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(player, serverConnection));
if (serverConnection != null) {
MinecraftConnection smc = serverConnection.ensureConnected();
CompletableFuture.runAsync(() -> {
Expand Down Expand Up @@ -512,7 +514,7 @@ public void writabilityChanged() {
* @return a future that completes when the switch is complete
*/
public CompletableFuture<Void> doSwitch() {
VelocityServerConnection existingConnection = player.getConnectedServer();
final VelocityServerConnection existingConnection = player.getConnectedServer();

if (existingConnection != null) {
// Shut down the existing server connection.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
import com.velocitypowered.api.event.player.PlayerModInfoEvent;
import com.velocitypowered.api.event.player.PlayerSettingsChangedEvent;
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.ProtocolVersion;
import com.velocitypowered.api.permission.PermissionFunction;
Expand All @@ -59,8 +60,9 @@
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.VelocityResourcePackInfo;
import com.velocitypowered.proxy.connection.player.resourcepack.ResourcePackHandler;
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;
import com.velocitypowered.proxy.connection.util.ConnectionMessages;
import com.velocitypowered.proxy.connection.util.ConnectionRequestResults.Impl;
import com.velocitypowered.proxy.connection.util.VelocityInboundConnection;
Expand Down Expand Up @@ -806,7 +808,7 @@ private void handleKickEvent(KickedFromServerEvent originalEvent, Component frie
}, connection.eventLoop());
} else if (event.getResult() instanceof final Notify res) {
if (event.kickedDuringServerConnect() && previousConnection != null) {
sendMessage(Identity.nil(), res.getMessageComponent());
sendMessage(res.getMessageComponent());
} else {
disconnect(res.getMessageComponent());
}
Expand Down Expand Up @@ -1224,11 +1226,12 @@ public void switchToConfigState() {
CompletableFuture.runAsync(() -> {
connection.write(StartUpdatePacket.INSTANCE);
connection.getChannel().pipeline()
.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
connection.addPlayPacketQueueHandler();
server.getEventManager().fireAndForget(new PlayerEnterConfigurationEvent(this, connectionInFlight));
}, 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;
});
}
Expand Down Expand Up @@ -1363,24 +1366,20 @@ public CompletableFuture<Boolean> connectWithIndication() {
}

switch (status.getStatus()) {
case ALREADY_CONNECTED:
sendMessage(Identity.nil(), ConnectionMessages.ALREADY_CONNECTED);
break;
case CONNECTION_IN_PROGRESS:
sendMessage(Identity.nil(), ConnectionMessages.IN_PROGRESS);
break;
case CONNECTION_CANCELLED:
case ALREADY_CONNECTED -> sendMessage(ConnectionMessages.ALREADY_CONNECTED);
case CONNECTION_IN_PROGRESS -> sendMessage(ConnectionMessages.IN_PROGRESS);
case CONNECTION_CANCELLED -> {
// Ignored; the plugin probably already handled this.
break;
case SERVER_DISCONNECTED:
Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
}
case SERVER_DISCONNECTED -> {
final Component reason = status.getReasonComponent()
.orElse(ConnectionMessages.INTERNAL_SERVER_CONNECTION_ERROR);
handleConnectionException(toConnect,
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe());
break;
default:
DisconnectPacket.create(reason, getProtocolVersion(), connection.getState()), status.isSafe());
}
default -> {
// The only remaining value is successful (no need to do anything!)
break;
}
}
}, connection.eventLoop()).thenApply(Result::isSuccessful);
}
Expand Down
Loading

0 comments on commit 220352d

Please sign in to comment.