From edf7b870c798a4e88e7afbcd188226d4bfa584af Mon Sep 17 00:00:00 2001 From: Matt Worzala <35708499+mworzala@users.noreply.github.com> Date: Sat, 5 Aug 2023 12:53:57 -0400 Subject: [PATCH] hollow-cube/add-gamemodechangeevent-and-fix-game-profile (#43) * Resolve some todos and add connection.setProfile to EncryptionResponsePacket * Add PlayerGameModeChangeEvent (cherry picked from commit 1514d8ac1f7c67e15896d8548e9d055999fec2f9) * Add unit tests (cherry picked from commit 9685e74f3b071317162cb2e64c0259561c9abf77) --------- Co-authored-by: GreatWyrm Co-authored-by: NxDs <7994264+NxDs@users.noreply.github.com> (cherry picked from commit 2c567696ac4e720b9b1e3a7b4bc38c9c16b15167) --- .../net/minestom/server/entity/Player.java | 14 ++++- .../player/PlayerGameModeChangeEvent.java | 56 +++++++++++++++++++ .../login/EncryptionResponsePacket.java | 23 +++++++- .../entity/player/PlayerIntegrationTest.java | 49 ++++++++++++---- 4 files changed, 127 insertions(+), 15 deletions(-) create mode 100644 src/main/java/net/minestom/server/event/player/PlayerGameModeChangeEvent.java diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index a13cfd533..bfa7d1dc7 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -1378,8 +1378,18 @@ public class Player extends LivingEntity implements CommandSender, Localizable, * Changes the player {@link GameMode} * * @param gameMode the new player GameMode + * @return true if the gamemode was changed successfully, false otherwise (cancelled by event) */ - public void setGameMode(@NotNull GameMode gameMode) { + public boolean setGameMode(@NotNull GameMode gameMode) { + PlayerGameModeChangeEvent playerGameModeChangeEvent = new PlayerGameModeChangeEvent(this, gameMode); + EventDispatcher.call(playerGameModeChangeEvent); + if (playerGameModeChangeEvent.isCancelled()) { + // Abort + return false; + } + + gameMode = playerGameModeChangeEvent.getNewGameMode(); + this.gameMode = gameMode; // Condition to prevent sending the packets before spawning the player if (isActive()) { @@ -1411,6 +1421,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, if (isActive()) { refreshAbilities(); } + + return true; } /** diff --git a/src/main/java/net/minestom/server/event/player/PlayerGameModeChangeEvent.java b/src/main/java/net/minestom/server/event/player/PlayerGameModeChangeEvent.java new file mode 100644 index 000000000..467766ce6 --- /dev/null +++ b/src/main/java/net/minestom/server/event/player/PlayerGameModeChangeEvent.java @@ -0,0 +1,56 @@ +package net.minestom.server.event.player; + +import net.minestom.server.entity.GameMode; +import net.minestom.server.entity.Player; +import net.minestom.server.event.trait.CancellableEvent; +import net.minestom.server.event.trait.PlayerInstanceEvent; +import org.jetbrains.annotations.NotNull; + +/** + * Called when the gamemode of a player is being modified. + */ +public class PlayerGameModeChangeEvent implements PlayerInstanceEvent, CancellableEvent { + + private final Player player; + private GameMode newGameMode; + + private boolean cancelled; + + public PlayerGameModeChangeEvent(@NotNull Player player, @NotNull GameMode newGameMode) { + this.player = player; + this.newGameMode = newGameMode; + } + + /** + * Gets the target gamemode. + * + * @return the target gamemode + */ + public @NotNull GameMode getNewGameMode() { + return newGameMode; + } + + /** + * Changes the target gamemode. + * + * @param newGameMode the new target gamemode + */ + public void setNewGameMode(@NotNull GameMode newGameMode) { + this.newGameMode = newGameMode; + } + + @Override + public boolean isCancelled() { + return cancelled; + } + + @Override + public void setCancelled(boolean cancel) { + this.cancelled = cancel; + } + + @Override + public @NotNull Player getPlayer() { + return player; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java index eaf57a25c..f40790ffe 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/EncryptionResponsePacket.java @@ -1,12 +1,15 @@ package net.minestom.server.network.packet.client.login; import com.google.gson.Gson; +import com.google.gson.JsonElement; import com.google.gson.JsonObject; +import net.kyori.adventure.text.Component; import net.minestom.server.MinecraftServer; import net.minestom.server.extras.MojangAuth; import net.minestom.server.extras.mojangAuth.MojangCrypt; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPreplayPacket; +import net.minestom.server.network.player.GameProfile; import net.minestom.server.network.player.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; import net.minestom.server.utils.async.AsyncUtils; @@ -20,7 +23,9 @@ import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; import java.util.Arrays; +import java.util.List; import java.util.UUID; import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; @@ -72,14 +77,22 @@ public record EncryptionResponsePacket(byte[] sharedSecret, client.sendAsync(request, HttpResponse.BodyHandlers.ofString()).whenComplete((response, throwable) -> { if (throwable != null) { MinecraftServer.getExceptionManager().handleException(throwable); - //todo disconnect with reason + if (socketConnection.getPlayer() != null) { + socketConnection.getPlayer().kick(Component.text("Failed to contact Mojang's Session Servers (Are they down?)")); + } else { + socketConnection.disconnect(); + } return; } try { final JsonObject gameProfile = GSON.fromJson(response.body(), JsonObject.class); if (gameProfile == null) { // Invalid response - //todo disconnect with reason + if (socketConnection.getPlayer() != null) { + socketConnection.getPlayer().kick(Component.text("Failed to get data from Mojang's Session Servers (Are they down?)")); + } else { + socketConnection.disconnect(); + } return; } socketConnection.setEncryptionKey(getSecretKey()); @@ -89,6 +102,12 @@ public record EncryptionResponsePacket(byte[] sharedSecret, MinecraftServer.LOGGER.info("UUID of player {} is {}", loginUsername, profileUUID); CONNECTION_MANAGER.startPlayState(connection, profileUUID, profileName, true); + List propertyList = new ArrayList<>(); + for (JsonElement element : gameProfile.get("properties").getAsJsonArray()) { + JsonObject object = element.getAsJsonObject(); + propertyList.add(new GameProfile.Property(object.get("name").getAsString(), object.get("value").getAsString(), object.get("signature").getAsString())); + } + socketConnection.UNSAFE_setProfile(new GameProfile(profileUUID, profileName, propertyList)); } catch (Exception e) { MinecraftServer.getExceptionManager().handleException(e); } diff --git a/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java b/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java index 68c42bc2b..d7b76895f 100644 --- a/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java +++ b/src/test/java/net/minestom/server/entity/player/PlayerIntegrationTest.java @@ -1,9 +1,11 @@ package net.minestom.server.entity.player; +import net.minestom.server.event.player.PlayerGameModeChangeEvent; import net.kyori.adventure.text.Component; import net.minestom.server.entity.damage.DamageType; import net.minestom.server.message.ChatMessageType; import net.minestom.server.network.packet.client.play.ClientSettingsPacket; +import net.minestom.server.event.player.PlayerGameModeChangeEvent; import net.minestom.testing.Collector; import net.minestom.testing.Env; import net.minestom.testing.EnvTest; @@ -27,7 +29,8 @@ import static org.junit.jupiter.api.Assertions.*; public class PlayerIntegrationTest { /** - * Test to see whether player abilities are updated correctly when changing gamemodes + * Test to see whether player abilities are updated correctly and events + * are handled properly when changing gamemode. */ @Test public void gamemodeTest(Env env) { @@ -36,16 +39,38 @@ public class PlayerIntegrationTest { var player = connection.connect(instance, new Pos(0, 42, 0)).join(); assertEquals(instance, player.getInstance()); - player.setGameMode(GameMode.CREATIVE); - assertAbilities(player, true, false, true, true); - player.setGameMode(GameMode.SPECTATOR); - assertAbilities(player, true, true, true, false); - player.setGameMode(GameMode.CREATIVE); - assertAbilities(player, true, true, true, true); - player.setGameMode(GameMode.ADVENTURE); - assertAbilities(player, false, false, false, false); - player.setGameMode(GameMode.SURVIVAL); - assertAbilities(player, false, false, false, false); + // Abilities + { + player.setGameMode(GameMode.CREATIVE); + assertAbilities(player, true, false, true, true); + player.setGameMode(GameMode.SPECTATOR); + assertAbilities(player, true, true, true, false); + player.setGameMode(GameMode.CREATIVE); + assertAbilities(player, true, true, true, true); + player.setGameMode(GameMode.ADVENTURE); + assertAbilities(player, false, false, false, false); + player.setGameMode(GameMode.SURVIVAL); + assertAbilities(player, false, false, false, false); + } + + var listener = env.listen(PlayerGameModeChangeEvent.class); + // Normal change + { + listener.followup(); + assertTrue(player.setGameMode(GameMode.ADVENTURE)); + } + // Change target gamemode event + { + listener.followup(event -> event.setNewGameMode(GameMode.SPECTATOR)); + assertTrue(player.setGameMode(GameMode.CREATIVE)); + assertEquals(GameMode.SPECTATOR, player.getGameMode()); + } + // Cancel event + { + listener.followup(event -> event.setCancelled(true)); + assertFalse(player.setGameMode(GameMode.CREATIVE)); + assertEquals(GameMode.SPECTATOR, player.getGameMode()); + } } @Test @@ -165,7 +190,7 @@ public class PlayerIntegrationTest { assertNull(player.getDeathLocation()); player.damage(DamageType.VOID, 30); - + assertNotNull(player.getDeathLocation()); assertEquals(dimensionNamespace, player.getDeathLocation().dimension()); assertEquals(5, player.getDeathLocation().position().x());