diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java new file mode 100644 index 000000000..6e042af1c --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerConfigurationEvent.java @@ -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 a player entered the configuration state and can be configured by Velocity. + *
Velocity will wait for this event before continuing/ending the configuration state.
+ * + * @param player The player who can be configured. + * @param server The server that is currently configuring the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +@AwaitingEvent +public record PlayerConfigurationEvent(@NotNull Player player, ServerConnection server) { +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java index 3b108c6a7..05d6c2af0 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnterConfigurationEvent.java @@ -7,21 +7,23 @@ package com.velocitypowered.api.event.player.configuration; -import com.velocitypowered.api.network.ProtocolState; +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 a player with version 1.20.2 or higher enters the configuration phase. - *From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed, - * the {@linkplain Player#getProtocolState()} method is guaranteed - * to return {@link ProtocolState#CONFIGURATION}.
+ * This event is executed when a player is about to enter the configuration state. + * It is not called for the initial configuration of a player after login. + *Velocity will wait for this event before asking the client to enter configuration state. + * However due to backend server being unable to keep the connection alive during state changes, + * Velocity will only wait for a maximum of 5 seconds.
* - * @param player The player that has entered the configuration phase. - * @param server The server that will now (re-)configure the player. + * @param player The player who is about to enter configuration state. + * @param server The server that wants to reconfigure the player. * @since 3.3.0 * @sinceMinecraft 1.20.2 */ +@AwaitingEvent public record PlayerEnterConfigurationEvent(@NotNull Player player, ServerConnection server) { } diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java new file mode 100644 index 000000000..c16777066 --- /dev/null +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerEnteredConfigurationEvent.java @@ -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 has entered the configuration state. + *From this moment on, until the {@link PlayerFinishedConfigurationEvent} is executed, + * the {@linkplain Player#getProtocolState()} method is guaranteed + * to return {@link ProtocolState#CONFIGURATION}.
+ * + * @param player The player who has entered the configuration state. + * @param server The server that will now (re-)configure the player. + * @since 3.3.0 + * @sinceMinecraft 1.20.2 + */ +public record PlayerEnteredConfigurationEvent(@NotNull Player player, ServerConnection server) { +} diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java index f6249b897..50df5a8ab 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishConfigurationEvent.java @@ -13,11 +13,14 @@ 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. - *Velocity will wait for this event to finish the configuration phase on the client.
+ * This event is executed when a player is about to finish the configuration state. + *Velocity will wait for this event before asking the client to finish the configuration state. + * However due to backend server being unable to keep the connection alive during state changes, + * Velocity will only wait for a maximum of 5 seconds. If you need to hold a player in configuration + * state, use the {@link PlayerConfigurationEvent}.
* - * @param player The player who is about to complete the configuration phase. - * @param server The server that is currently (re-)configuring the player. + * @param player The player who is about to finish the configuration phase. + * @param server The server that has (re-)configured the player. * @since 3.3.0 * @sinceMinecraft 1.20.2 */ diff --git a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java index 09e76104f..517f119cf 100644 --- a/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java +++ b/api/src/main/java/com/velocitypowered/api/event/player/configuration/PlayerFinishedConfigurationEvent.java @@ -13,11 +13,11 @@ 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. + * This event is executed when a player has finished the configuration state. *From this moment on, the {@link Player#getProtocolState()} method * will return {@link ProtocolState#PLAY}.
* - * @param player The player who has completed the Configuration state + * @param player The player who has finished the configuration state. * @param server The server that has (re-)configured the player. * @since 3.3.0 * @sinceMinecraft 1.20.2 diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java index a2d2205ef..0fdb5fa25 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/backend/LoginSessionHandler.java @@ -19,6 +19,7 @@ package com.velocitypowered.proxy.connection.backend; import com.velocitypowered.api.event.player.CookieRequestEvent; import com.velocitypowered.api.event.player.ServerLoginPluginMessageEvent; +import com.velocitypowered.api.event.player.configuration.PlayerEnteredConfigurationEvent; import com.velocitypowered.api.network.ProtocolVersion; import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier; import com.velocitypowered.proxy.VelocityServer; @@ -142,10 +143,8 @@ public class LoginSessionHandler implements MinecraftSessionHandler { @Override public boolean handle(ServerLoginSuccessPacket packet) { - if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN - && !informationForwarded) { - resultFuture.complete(ConnectionRequestResults.forDisconnect(MODERN_IP_FORWARDING_FAILURE, - serverConn.getServer())); + if (server.getConfiguration().getPlayerInfoForwardingMode() == PlayerInfoForwarding.MODERN && !informationForwarded) { + resultFuture.complete(ConnectionRequestResults.forDisconnect(MODERN_IP_FORWARDING_FAILURE, serverConn.getServer())); serverConn.disconnect(); return true; } @@ -156,12 +155,10 @@ public class LoginSessionHandler implements MinecraftSessionHandler { // Move into the PLAY phase. MinecraftConnection smc = serverConn.ensureConnected(); if (smc.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { - smc.setActiveSessionHandler(StateRegistry.PLAY, - new TransitionSessionHandler(server, serverConn, resultFuture)); + smc.setActiveSessionHandler(StateRegistry.PLAY, new TransitionSessionHandler(server, serverConn, resultFuture)); } else { smc.write(new LoginAcknowledgedPacket()); - smc.setActiveSessionHandler(StateRegistry.CONFIG, - new ConfigSessionHandler(server, serverConn, resultFuture)); + smc.setActiveSessionHandler(StateRegistry.CONFIG, new ConfigSessionHandler(server, serverConn, resultFuture)); ConnectedPlayer player = serverConn.getPlayer(); if (player.getClientSettingsPacket() != null) { smc.write(player.getClientSettingsPacket()); @@ -169,6 +166,9 @@ public class LoginSessionHandler implements MinecraftSessionHandler { if (player.getConnection().getActiveSessionHandler() instanceof ClientPlaySessionHandler clientPlaySessionHandler) { smc.setAutoReading(false); clientPlaySessionHandler.doSwitch().thenAcceptAsync((unused) -> smc.setAutoReading(true), smc.eventLoop()); + } else { + // Initial login - the player is already in configuration state. + server.getEventManager().fireAndForget(new PlayerEnteredConfigurationEvent(player, serverConn)); } } diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java index 77290584d..ac02bcf76 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/AuthSessionHandler.java @@ -178,14 +178,14 @@ public class AuthSessionHandler implements MinecraftSessionHandler { inbound.disconnect(Component.translatable("multiplayer.disconnect.invalid_player_data")); } else { loginState = State.ACKNOWLEDGED; - mcConnection.setActiveSessionHandler(StateRegistry.CONFIG, - new ClientConfigSessionHandler(server, connectedPlayer)); + mcConnection.setActiveSessionHandler(StateRegistry.CONFIG, new ClientConfigSessionHandler(server, connectedPlayer)); - server.getEventManager().fire(new PostLoginEvent(connectedPlayer)) - .thenCompose((ignored) -> connectToInitialServer(connectedPlayer)).exceptionally((ex) -> { - logger.error("Exception while connecting {} to initial server", connectedPlayer, ex); - return null; - }); + server.getEventManager().fire(new PostLoginEvent(connectedPlayer)).thenCompose(ignored -> { + return connectToInitialServer(connectedPlayer); + }).exceptionally((ex) -> { + logger.error("Exception while connecting {} to initial server", connectedPlayer, ex); + return null; + }); } return true; } @@ -224,8 +224,7 @@ public class AuthSessionHandler implements MinecraftSessionHandler { player.disconnect0(reason.get(), true); } else { if (!server.registerConnection(player)) { - player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), - true); + player.disconnect0(Component.translatable("velocity.error.already-connected-proxy"), true); return; } @@ -238,13 +237,13 @@ public class AuthSessionHandler implements MinecraftSessionHandler { loginState = State.SUCCESS_SENT; if (inbound.getProtocolVersion().lessThan(ProtocolVersion.MINECRAFT_1_20_2)) { loginState = State.ACKNOWLEDGED; - mcConnection.setActiveSessionHandler(StateRegistry.PLAY, - new InitialConnectSessionHandler(player, server)); - server.getEventManager().fire(new PostLoginEvent(player)) - .thenCompose((ignored) -> connectToInitialServer(player)).exceptionally((ex) -> { - logger.error("Exception while connecting {} to initial server", player, ex); - return null; - }); + mcConnection.setActiveSessionHandler(StateRegistry.PLAY, new InitialConnectSessionHandler(player, server)); + server.getEventManager().fire(new PostLoginEvent(player)).thenCompose((ignored) -> { + return connectToInitialServer(player); + }).exceptionally((ex) -> { + logger.error("Exception while connecting {} to initial server", player, ex); + return null; + }); } } }, mcConnection.eventLoop()).exceptionally((ex) -> { diff --git a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java index 3d955e903..7d232b09f 100644 --- a/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java +++ b/proxy/src/main/java/com/velocitypowered/proxy/connection/client/ClientConfigSessionHandler.java @@ -19,6 +19,7 @@ package com.velocitypowered.proxy.connection.client; import com.velocitypowered.api.event.player.CookieReceiveEvent; import com.velocitypowered.api.event.player.PlayerClientBrandEvent; +import com.velocitypowered.api.event.player.configuration.PlayerConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerFinishConfigurationEvent; import com.velocitypowered.api.event.player.configuration.PlayerFinishedConfigurationEvent; import com.velocitypowered.proxy.VelocityServer; @@ -48,8 +49,6 @@ import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; /** * Handles the client config stage. @@ -61,6 +60,7 @@ public class ClientConfigSessionHandler implements MinecraftSessionHandler { private final ConnectedPlayer player; private String brandChannel = null; + private CompletableFuture> configurationFuture; private CompletableFuture