feat: add AsyncPlayerConfigurationEvent, remove PlayerLoginEvent

(cherry picked from commit 1161fbf1b1)
This commit is contained in:
mworzala 2023-11-07 10:19:05 +00:00 committed by Matt Worzala
parent bad9e80c88
commit 5caffb134c
9 changed files with 89 additions and 162 deletions

View File

@ -77,7 +77,7 @@ public class PlayerInit {
itemEntity.setVelocity(velocity); itemEntity.setVelocity(velocity);
}) })
.addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername())) .addListener(PlayerDisconnectEvent.class, event -> System.out.println("DISCONNECTION " + event.getPlayer().getUsername()))
.addListener(PlayerLoginEvent.class, event -> { .addListener(AsyncPlayerConfigurationEvent.class, event -> {
final Player player = event.getPlayer(); final Player player = event.getPlayer();
var instances = MinecraftServer.getInstanceManager().getInstances(); var instances = MinecraftServer.getInstanceManager().getInstances();

View File

@ -61,6 +61,7 @@ import net.minestom.server.network.packet.client.ClientPacket;
import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.SendablePacket;
import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacket;
import net.minestom.server.network.packet.server.common.*; import net.minestom.server.network.packet.server.common.*;
import net.minestom.server.network.packet.server.configuration.FinishConfigurationPacket;
import net.minestom.server.network.packet.server.login.LoginDisconnectPacket; import net.minestom.server.network.packet.server.login.LoginDisconnectPacket;
import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.packet.server.play.*;
import net.minestom.server.network.packet.server.play.data.DeathLocation; import net.minestom.server.network.packet.server.play.data.DeathLocation;
@ -248,6 +249,19 @@ public class Player extends LivingEntity implements CommandSender, Localizable,
metadata.setNotifyAboutChanges(false); metadata.setNotifyAboutChanges(false);
} }
public void UNSAFE_enterConfiguration(boolean isFirstConfig) {
AsyncUtils.runAsync(() -> {
var event = new AsyncPlayerConfigurationEvent(this, isFirstConfig);
EventDispatcher.call(event);
final Instance spawningInstance = event.getSpawningInstance();
Check.notNull(spawningInstance, "You need to specify a spawning instance in the AsyncPlayerConfigurationEvent");
MinecraftServer.getConnectionManager().startPlayState(this, event.getSpawningInstance());
sendPacket(new FinishConfigurationPacket());
});
}
/** /**
* Used when the player is created. * Used when the player is created.
* Init the player and spawn him. * Init the player and spawn him.

View File

@ -67,7 +67,7 @@ public class FakePlayer extends Player implements NavigableEntity {
}).build(); }).build();
MinecraftServer.getGlobalEventHandler().addListener(spawnListener); MinecraftServer.getGlobalEventHandler().addListener(spawnListener);
} }
CONNECTION_MANAGER.startConfigurationState(this, option.isRegistered()); CONNECTION_MANAGER.startConfigurationState(this);
} }
/** /**

View File

@ -0,0 +1,48 @@
package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerEvent;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called when a player enters the configuration state (either on first connection, or if they are
* sent back to configuration later). The player is moved to the play state as soon as all event
* handles finish processing this event.
*
* <p>The spawning instance <b>must</b> be set for the player to join.</p>
*
* <p>The event is called off the tick threads, so it is safe to block here</p>
*/
public class AsyncPlayerConfigurationEvent implements PlayerEvent {
private final Player player;
private final boolean isFirstConfig;
private Instance spawningInstance = null;
public AsyncPlayerConfigurationEvent(@NotNull Player player, boolean isFirstConfig) {
this.player = player;
this.isFirstConfig = isFirstConfig;
}
@Override
public @NotNull Player getPlayer() {
return this.player;
}
/**
* Returns true if this is the first time the player is in the configuration phase (they are joining), false otherwise.
*/
public boolean isFirstConfig() {
return isFirstConfig;
}
public @Nullable Instance getSpawningInstance() {
return spawningInstance;
}
public void setSpawningInstance(@Nullable Instance spawningInstance) {
this.spawningInstance = spawningInstance;
}
}

View File

@ -1,53 +0,0 @@
package net.minestom.server.event.player;
import net.minestom.server.entity.Player;
import net.minestom.server.event.trait.PlayerEvent;
import net.minestom.server.instance.Instance;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
* Called at player login, used to define his spawn instance.
* <p>
* Be aware that the player is not yet in a world when the event
* is called, meaning that most player methods will not work.
* You can use {@link PlayerSpawnEvent} and {@link PlayerSpawnEvent#isFirstSpawn()}
* if needed.
* <p>
* WARNING: defining the spawning instance is MANDATORY.
*/
public class PlayerLoginEvent implements PlayerEvent {
private final Player player;
private Instance spawningInstance;
public PlayerLoginEvent(@NotNull Player player) {
this.player = player;
}
/**
* Gets the spawning instance of the player.
* <p>
* WARNING: this must NOT be null, otherwise the player cannot spawn.
*
* @return the spawning instance, null if not already defined
*/
@Nullable
public Instance getSpawningInstance() {
return spawningInstance;
}
/**
* Changes the spawning instance.
*
* @param instance the new spawning instance
*/
public void setSpawningInstance(@NotNull Instance instance) {
this.spawningInstance = instance;
}
@Override
public @NotNull Player getPlayer() {
return player;
}
}

View File

@ -12,10 +12,9 @@ public final class ConfigurationListener {
private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager(); private static final ConnectionManager CONNECTION_MANAGER = MinecraftServer.getConnectionManager();
public static void finishListener(@NotNull ClientFinishConfigurationPacket packet, @NotNull Player player) { public static void finishListener(@NotNull ClientFinishConfigurationPacket packet, @NotNull Player player) {
player.getPlayerConnection().setClientState(ConnectionState.PLAY); // player.getPlayerConnection().setClientState(ConnectionState.PLAY);
System.out.println("Finished configuration for " + player.getUsername() ); System.out.println("Finished configuration for " + player.getUsername() );
CONNECTION_MANAGER.startPlayState(player);
//todo move to play state //todo move to play state

View File

@ -6,10 +6,15 @@ import com.google.gson.JsonObject;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.NamedTextColor;
import net.minestom.server.MinecraftServer; import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player;
import net.minestom.server.entity.damage.DamageType;
import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.player.AsyncPlayerConfigurationEvent;
import net.minestom.server.extras.MojangAuth; import net.minestom.server.extras.MojangAuth;
import net.minestom.server.extras.bungee.BungeeCordProxy; import net.minestom.server.extras.bungee.BungeeCordProxy;
import net.minestom.server.extras.mojangAuth.MojangCrypt; import net.minestom.server.extras.mojangAuth.MojangCrypt;
import net.minestom.server.extras.velocity.VelocityProxy; import net.minestom.server.extras.velocity.VelocityProxy;
import net.minestom.server.instance.Instance;
import net.minestom.server.message.Messenger; import net.minestom.server.message.Messenger;
import net.minestom.server.network.ConnectionManager; import net.minestom.server.network.ConnectionManager;
import net.minestom.server.network.ConnectionState; import net.minestom.server.network.ConnectionState;
@ -30,6 +35,7 @@ import net.minestom.server.network.player.PlayerConnection;
import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.network.player.PlayerSocketConnection;
import net.minestom.server.registry.Registry; import net.minestom.server.registry.Registry;
import net.minestom.server.utils.async.AsyncUtils; import net.minestom.server.utils.async.AsyncUtils;
import net.minestom.server.utils.validate.Check;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jglrxavpok.hephaistos.nbt.NBT; import org.jglrxavpok.hephaistos.nbt.NBT;
import org.jglrxavpok.hephaistos.nbt.NBTType; import org.jglrxavpok.hephaistos.nbt.NBTType;
@ -92,7 +98,7 @@ public final class LoginListener {
final UUID playerUuid = bungee && isSocketConnection ? final UUID playerUuid = bungee && isSocketConnection ?
((PlayerSocketConnection) connection).gameProfile().uuid() : ((PlayerSocketConnection) connection).gameProfile().uuid() :
CONNECTION_MANAGER.getPlayerConnectionUuid(connection, packet.username()); CONNECTION_MANAGER.getPlayerConnectionUuid(connection, packet.username());
CONNECTION_MANAGER.startConfigurationState(connection, playerUuid, packet.username(), true); CONNECTION_MANAGER.startConfigurationState(connection, playerUuid, packet.username());
} }
} }
@ -158,7 +164,7 @@ public final class LoginListener {
final String profileName = gameProfile.get("name").getAsString(); final String profileName = gameProfile.get("name").getAsString();
MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, profileUUID); MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, profileUUID);
CONNECTION_MANAGER.startConfigurationState(connection, profileUUID, profileName, true); CONNECTION_MANAGER.startConfigurationState(connection, profileUUID, profileName);
List<GameProfile.Property> propertyList = new ArrayList<>(); List<GameProfile.Property> propertyList = new ArrayList<>();
for (JsonElement element : gameProfile.get("properties").getAsJsonArray()) { for (JsonElement element : gameProfile.get("properties").getAsJsonArray()) {
JsonObject object = element.getAsJsonObject(); JsonObject object = element.getAsJsonObject();
@ -211,7 +217,7 @@ public final class LoginListener {
if (success) { if (success) {
socketConnection.setRemoteAddress(socketAddress); socketConnection.setRemoteAddress(socketAddress);
socketConnection.UNSAFE_setProfile(gameProfile); socketConnection.UNSAFE_setProfile(gameProfile);
CONNECTION_MANAGER.startConfigurationState(connection, gameProfile.uuid(), gameProfile.name(), true); CONNECTION_MANAGER.startConfigurationState(connection, gameProfile.uuid(), gameProfile.name());
} else { } else {
LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE); LoginDisconnectPacket disconnectPacket = new LoginDisconnectPacket(INVALID_PROXY_RESPONSE);
socketConnection.sendPacket(disconnectPacket); socketConnection.sendPacket(disconnectPacket);
@ -221,47 +227,22 @@ public final class LoginListener {
} }
public static void loginAckListener(@NotNull ClientLoginAcknowledgedPacket ignored, @NotNull PlayerConnection connection) { public static void loginAckListener(@NotNull ClientLoginAcknowledgedPacket ignored, @NotNull PlayerConnection connection) {
CONNECTION_MANAGER.registerPlayer(connection.getPlayer()); final Player player = Objects.requireNonNull(connection.getPlayer());
CONNECTION_MANAGER.registerPlayer(player);
// Registry data // Registry data
var registry = new HashMap<String, NBT>(); var registry = new HashMap<String, NBT>();
registry.put("minecraft:chat_type", Messenger.chatRegistry()); registry.put("minecraft:chat_type", Messenger.chatRegistry());
registry.put("minecraft:dimension_type", MinecraftServer.getDimensionTypeManager().toNBT()); registry.put("minecraft:dimension_type", MinecraftServer.getDimensionTypeManager().toNBT());
registry.put("minecraft:worldgen/biome", MinecraftServer.getBiomeManager().toNBT()); registry.put("minecraft:worldgen/biome", MinecraftServer.getBiomeManager().toNBT());
var damageTypeData = Registry.load(Registry.Resource.DAMAGE_TYPES); registry.put("minecraft:damage_type", DamageType.getNBT());
var damageTypes = new ArrayList<NBT>();
int i = 0;
for (var entry : damageTypeData.entrySet()) {
var elem = new HashMap<String, NBT>();
for (var e : entry.getValue().entrySet()) {
if (e.getValue() instanceof String s) {
elem.put(e.getKey(), NBT.String(s));
} else if (e.getValue() instanceof Double f) {
elem.put(e.getKey(), NBT.Float(f.floatValue()));
} else if (e.getValue() instanceof Integer integer) {
elem.put(e.getKey(), NBT.Int(integer));
}
}
damageTypes.add(NBT.Compound(Map.of(
"id", NBT.Int(i++),
"name", NBT.String(entry.getKey()),
"element", NBT.Compound(elem)
)));
}
registry.put("minecraft:damage_type", NBT.Compound(Map.of(
"type", NBT.String("minecraft:damage_type"),
"value", NBT.List(NBTType.TAG_Compound, damageTypes)
)));
connection.sendPacket(new RegistryDataPacket(NBT.Compound(registry))); connection.sendPacket(new RegistryDataPacket(NBT.Compound(registry)));
// Tags // Tags
connection.sendPacket(TagsPacket.DEFAULT_TAGS); connection.sendPacket(TagsPacket.DEFAULT_TAGS);
AsyncUtils.runAsync(() -> { // Enter configuration phase (for the first time)
//todo event player.UNSAFE_enterConfiguration(true);
connection.sendPacket(new FinishConfigurationPacket());
});
} }
} }

View File

@ -6,7 +6,6 @@ import net.minestom.server.MinecraftServer;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.event.EventDispatcher; import net.minestom.server.event.EventDispatcher;
import net.minestom.server.event.player.AsyncPlayerPreLoginEvent; import net.minestom.server.event.player.AsyncPlayerPreLoginEvent;
import net.minestom.server.event.player.PlayerLoginEvent;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.network.packet.client.login.ClientLoginStartPacket; import net.minestom.server.network.packet.client.login.ClientLoginStartPacket;
import net.minestom.server.network.packet.server.login.LoginSuccessPacket; import net.minestom.server.network.packet.server.login.LoginSuccessPacket;
@ -36,7 +35,9 @@ public final class ConnectionManager {
private static final long KEEP_ALIVE_KICK = 30_000; private static final long KEEP_ALIVE_KICK = 30_000;
private static final Component TIMEOUT_TEXT = Component.text("Timeout", NamedTextColor.RED); private static final Component TIMEOUT_TEXT = Component.text("Timeout", NamedTextColor.RED);
private final MessagePassingQueue<Player> waitingPlayers = new MpscUnboundedArrayQueue<>(64); private record PendingPlayer(@NotNull Player player, @NotNull Instance instance) {}
private final MessagePassingQueue<PendingPlayer> waitingPlayers = new MpscUnboundedArrayQueue<>(64);
private final Set<Player> players = new CopyOnWriteArraySet<>(); private final Set<Player> players = new CopyOnWriteArraySet<>();
private final Set<Player> unmodifiablePlayers = Collections.unmodifiableSet(players); private final Set<Player> unmodifiablePlayers = Collections.unmodifiableSet(players);
private final Map<PlayerConnection, Player> connectionPlayerMap = new ConcurrentHashMap<>(); private final Map<PlayerConnection, Player> connectionPlayerMap = new ConcurrentHashMap<>();
@ -186,14 +187,13 @@ public final class ConnectionManager {
} }
public @NotNull Player startConfigurationState(@NotNull PlayerConnection connection, public @NotNull Player startConfigurationState(@NotNull PlayerConnection connection,
@NotNull UUID uuid, @NotNull String username, @NotNull UUID uuid, @NotNull String username) {
boolean register) {
final Player player = playerProvider.createPlayer(uuid, username, connection); final Player player = playerProvider.createPlayer(uuid, username, connection);
startConfigurationState(player, register); startConfigurationState(player);
return player; return player;
} }
public CompletableFuture<Void> startConfigurationState(@NotNull Player player, boolean register) { public CompletableFuture<Void> startConfigurationState(@NotNull Player player) {
return AsyncUtils.runAsync(() -> { return AsyncUtils.runAsync(() -> {
final PlayerConnection playerConnection = player.getPlayerConnection(); final PlayerConnection playerConnection = player.getPlayerConnection();
// Compression // Compression
@ -224,65 +224,8 @@ public final class ConnectionManager {
}); });
} }
public void startPlayState(@NotNull Player player) { public void startPlayState(@NotNull Player player, @NotNull Instance instance) {
this.waitingPlayers.relaxedOffer(player); this.waitingPlayers.relaxedOffer(new PendingPlayer(player, instance));
}
/**
* Calls the player initialization callbacks and the event {@link AsyncPlayerPreLoginEvent}.
* <p>
* Sends a {@link LoginSuccessPacket} if successful (not kicked)
* and change the connection state to {@link ConnectionState#PLAY}.
*
* @param player the player
* @param register true to register the newly created player in {@link ConnectionManager} lists
*/
public CompletableFuture<Void> startPlayStateOLD(@NotNull Player player, boolean register) {
return AsyncUtils.runAsync(() -> {
final PlayerConnection playerConnection = player.getPlayerConnection();
// Compression
if (playerConnection instanceof PlayerSocketConnection socketConnection) {
final int threshold = MinecraftServer.getCompressionThreshold();
if (threshold > 0) socketConnection.startCompression();
}
// Call pre login event
AsyncPlayerPreLoginEvent asyncPlayerPreLoginEvent = new AsyncPlayerPreLoginEvent(player);
EventDispatcher.call(asyncPlayerPreLoginEvent);
if (!player.isOnline())
return; // Player has been kicked
// Change UUID/Username based on the event
{
final String eventUsername = asyncPlayerPreLoginEvent.getUsername();
final UUID eventUuid = asyncPlayerPreLoginEvent.getPlayerUuid();
if (!player.getUsername().equals(eventUsername)) {
player.setUsernameField(eventUsername);
}
if (!player.getUuid().equals(eventUuid)) {
player.setUuid(eventUuid);
}
}
// Send login success packet
LoginSuccessPacket loginSuccessPacket = new LoginSuccessPacket(player.getUuid(), player.getUsername(), 0);
playerConnection.sendPacket(loginSuccessPacket);
// playerConnection.setConnectionState(ConnectionState.PLAY);
if (register) registerPlayer(player);
this.waitingPlayers.relaxedOffer(player);
});
}
/**
* Creates a {@link Player} using the defined {@link PlayerProvider}
* and execute {@link #startPlayState(Player, boolean)}.
*
* @return the newly created player object
* @see #startPlayState(Player, boolean)
*/
public @NotNull Player startPlayStateOLD(@NotNull PlayerConnection connection,
@NotNull UUID uuid, @NotNull String username,
boolean register) {
final Player player = playerProvider.createPlayer(uuid, username, connection);
startPlayStateOLD(player, register);
return player;
} }
/** /**
@ -297,14 +240,10 @@ public final class ConnectionManager {
* Connects waiting players. * Connects waiting players.
*/ */
public void updateWaitingPlayers() { public void updateWaitingPlayers() {
this.waitingPlayers.drain(waitingPlayer -> { this.waitingPlayers.drain(pp -> {
PlayerLoginEvent loginEvent = new PlayerLoginEvent(waitingPlayer);
EventDispatcher.call(loginEvent);
final Instance spawningInstance = loginEvent.getSpawningInstance();
Check.notNull(spawningInstance, "You need to specify a spawning instance in the PlayerLoginEvent");
// Spawn the player at Player#getRespawnPoint // Spawn the player at Player#getRespawnPoint
CompletableFuture<Void> spawnFuture = waitingPlayer.UNSAFE_init(spawningInstance); CompletableFuture<Void> spawnFuture = pp.player.UNSAFE_init(pp.instance);
// Required to get the exact moment the player spawns // Required to get the exact moment the player spawns
if (DebugUtils.INSIDE_TEST) spawnFuture.join(); if (DebugUtils.INSIDE_TEST) spawnFuture.join();

View File

@ -5,7 +5,6 @@ import net.minestom.server.ServerProcess;
import net.minestom.server.adventure.MinestomAdventure; import net.minestom.server.adventure.MinestomAdventure;
import net.minestom.server.coordinate.Pos; import net.minestom.server.coordinate.Pos;
import net.minestom.server.entity.Player; import net.minestom.server.entity.Player;
import net.minestom.server.event.player.PlayerLoginEvent;
import net.minestom.server.instance.Instance; import net.minestom.server.instance.Instance;
import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ComponentHoldingServerPacket;
import net.minestom.server.network.packet.server.SendablePacket; import net.minestom.server.network.packet.server.SendablePacket;