diff --git a/demo/build.gradle.kts b/demo/build.gradle.kts index 60b18683d..e84143ee7 100644 --- a/demo/build.gradle.kts +++ b/demo/build.gradle.kts @@ -17,6 +17,8 @@ application { dependencies { implementation(rootProject) implementation(libs.jNoise) + implementation("ch.qos.logback:logback-core:1.4.5") + implementation("ch.qos.logback:logback-classic:1.4.5") } tasks.withType { diff --git a/demo/src/main/java/net/minestom/demo/Main.java b/demo/src/main/java/net/minestom/demo/Main.java index 4030fd3d7..a6447994e 100644 --- a/demo/src/main/java/net/minestom/demo/Main.java +++ b/demo/src/main/java/net/minestom/demo/Main.java @@ -1,5 +1,6 @@ package net.minestom.demo; +import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.text.format.Style; @@ -8,6 +9,7 @@ import net.kyori.adventure.text.format.TextDecoration; import net.minestom.demo.commands.*; import net.minestom.server.MinecraftServer; import net.minestom.server.command.CommandManager; +import net.minestom.server.event.player.PlayerChatEvent; import net.minestom.server.event.server.ServerListPingEvent; import net.minestom.server.extras.lan.OpenToLAN; import net.minestom.server.extras.lan.OpenToLANConfig; @@ -15,6 +17,7 @@ import net.minestom.server.extras.optifine.OptifineSupport; import net.minestom.server.instance.block.BlockManager; import net.minestom.server.instance.block.rule.vanilla.RedstonePlacementRule; import net.minestom.server.ping.ResponseData; +import net.minestom.server.sound.SoundEvent; import net.minestom.server.utils.identity.NamedAndIdentified; import net.minestom.server.utils.time.TimeUnit; @@ -96,6 +99,27 @@ public class Main { //responseData.setPlayersHidden(true); }); + MinecraftServer.getGlobalEventHandler().addListener(PlayerChatEvent.class, e -> { + var playPos = e.getPlayer().getPosition().add(5.0, 0.0, 0.0); + + switch (e.getMessage()) { + case "a" -> { + System.out.println("with position"); + e.getPlayer().playSound(Sound.sound(SoundEvent.AMBIENT_CRIMSON_FOREST_MOOD, Sound.Source.MASTER, 1f, 1f), playPos.x(), playPos.y(), playPos.z()); + } + case "b" -> { + System.out.println("without anything"); + e.getPlayer().playSound(Sound.sound(SoundEvent.AMBIENT_CRIMSON_FOREST_MOOD, Sound.Source.MASTER, 1f, 1f)); + } + case "c" -> { + System.out.println("with self emitter"); + e.getPlayer().playSound(Sound.sound(SoundEvent.AMBIENT_CRIMSON_FOREST_MOOD, Sound.Source.MASTER, 1f, 1f), Sound.Emitter.self()); + } + } + + + }); + PlayerInit.init(); OptifineSupport.enable(); diff --git a/demo/src/main/java/net/minestom/demo/commands/TestCommand.java b/demo/src/main/java/net/minestom/demo/commands/TestCommand.java index c2294cb9b..75482a678 100644 --- a/demo/src/main/java/net/minestom/demo/commands/TestCommand.java +++ b/demo/src/main/java/net/minestom/demo/commands/TestCommand.java @@ -1,10 +1,14 @@ package net.minestom.demo.commands; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.sound.Sound; import net.kyori.adventure.text.Component; +import net.minestom.server.adventure.AdventurePacketConvertor; import net.minestom.server.command.CommandSender; import net.minestom.server.command.builder.Command; import net.minestom.server.command.builder.CommandContext; import net.minestom.server.command.builder.arguments.ArgumentType; +import net.minestom.server.sound.SoundEvent; public class TestCommand extends Command { @@ -15,7 +19,12 @@ public class TestCommand extends Command { var block = ArgumentType.BlockState("block"); block.setCallback((sender, exception) -> exception.printStackTrace()); + setDefaultExecutor((sender, context) -> { + sender.playSound(Sound.sound(Key.key("item.trumpet.doot"), Sound.Source.PLAYER, 1, 1)); + AdventurePacketConvertor.createSoundPacket(Sound.sound(Key.key(SoundEvent.BLOCK_ANVIL_HIT.name()), Sound.Source.HOSTILE, 1, 1), sender.asPlayer()); + }); addSyntax((sender, context) -> System.out.println("executed"), block); + } private void usage(CommandSender sender, CommandContext context) { diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 2d34cb966..96861dcb0 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -7,7 +7,7 @@ adventure = "4.12.0" kotlin = "1.7.22" hydrazine = "1.7.2" dependencyGetter = "v1.0.1" -minestomData = "1c1921cd41" +minestomData = "53e0da5be1" hephaistos = "2.5.3" jetbrainsAnnotations = "23.0.0" diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index 077c6f728..059ebc19b 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -45,8 +45,8 @@ public final class MinecraftServer { public static final ComponentLogger LOGGER = ComponentLogger.logger(MinecraftServer.class); - public static final String VERSION_NAME = "1.19.2"; - public static final int PROTOCOL_VERSION = 760; + public static final String VERSION_NAME = "1.19.3"; + public static final int PROTOCOL_VERSION = 761; // Threads public static final String THREAD_NAME_BENCHMARK = "Ms-Benchmark"; diff --git a/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java b/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java index d5ef48364..04244d43a 100644 --- a/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java +++ b/src/main/java/net/minestom/server/adventure/AdventurePacketConvertor.java @@ -11,6 +11,7 @@ import net.kyori.adventure.text.format.NamedTextColor; import net.kyori.adventure.title.Title; import net.kyori.adventure.title.TitlePart; import net.minestom.server.coordinate.Pos; +import net.minestom.server.coordinate.Vec; import net.minestom.server.entity.Entity; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.play.*; @@ -111,11 +112,11 @@ public class AdventurePacketConvertor { public static @NotNull ServerPacket createSoundPacket(@NotNull Sound sound, double x, double y, double z) { final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString()); if (minestomSound == null) { - return new NamedSoundEffectPacket(sound.name().asString(), sound.source(), - (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), 0); + return new SoundEffectPacket(sound.name().asString(), null, sound.source(), + new Vec(x, y, z), sound.volume(), sound.pitch(), 0); } else { - return new SoundEffectPacket(minestomSound.id(), sound.source(), - (int) x, (int) y, (int) z, sound.volume(), sound.pitch(), 0); + return new SoundEffectPacket(minestomSound, null, sound.source(), + new Vec(x, y, z), sound.volume(), sound.pitch(), 0); } } @@ -135,11 +136,9 @@ public class AdventurePacketConvertor { final SoundEvent minestomSound = SoundEvent.fromNamespaceId(sound.name().asString()); if (minestomSound != null) { - return new EntitySoundEffectPacket(minestomSound.id(), sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), 0); + return new EntitySoundEffectPacket(minestomSound, null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), 0); } else { - final Pos pos = entity.getPosition(); - return new NamedSoundEffectPacket(sound.name().asString(), sound.source(), - (int) pos.x(), (int) pos.y(), (int) pos.z(), sound.volume(), sound.pitch(), 0); + return new EntitySoundEffectPacket(sound.name().asString(), null, sound.source(), entity.getEntityId(), sound.volume(), sound.pitch(), 0); } } diff --git a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java index c2e67ae0f..baf582df2 100644 --- a/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java +++ b/src/main/java/net/minestom/server/command/builder/arguments/minecraft/registry/ArgumentPotionEffect.java @@ -14,7 +14,8 @@ public class ArgumentPotionEffect extends ArgumentRegistry { @Override public String parser() { - return "minecraft:mob_effect"; + // TODO: what replace `minecraft:mob_effect`? + return "minecraft:uuid"; } @Override diff --git a/src/main/java/net/minestom/server/crypto/ChatBound.java b/src/main/java/net/minestom/server/crypto/ChatBound.java new file mode 100644 index 000000000..5f2c7dc66 --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/ChatBound.java @@ -0,0 +1,22 @@ +package net.minestom.server.crypto; + +import net.kyori.adventure.text.Component; +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import static net.minestom.server.network.NetworkBuffer.COMPONENT; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; + +public record ChatBound(int chatType, Component name, @Nullable Component targetName) implements NetworkBuffer.Writer { + public ChatBound(@NotNull NetworkBuffer reader) { + this(reader.read(VAR_INT), reader.read(COMPONENT), reader.readOptional(COMPONENT)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(VAR_INT, chatType); + writer.write(COMPONENT, name); + writer.writeOptional(COMPONENT, targetName); + } +} diff --git a/src/main/java/net/minestom/server/crypto/ChatSession.java b/src/main/java/net/minestom/server/crypto/ChatSession.java new file mode 100644 index 000000000..1cdcd253b --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/ChatSession.java @@ -0,0 +1,18 @@ +package net.minestom.server.crypto; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.util.UUID; + +public record ChatSession(@NotNull UUID sessionId, @NotNull PlayerPublicKey publicKey) implements NetworkBuffer.Writer { + public ChatSession(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.UUID), new PlayerPublicKey(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.UUID, sessionId); + writer.write(publicKey); + } +} diff --git a/src/main/java/net/minestom/server/crypto/FilterMask.java b/src/main/java/net/minestom/server/crypto/FilterMask.java new file mode 100644 index 000000000..2ffe575d4 --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/FilterMask.java @@ -0,0 +1,28 @@ +package net.minestom.server.crypto; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.util.BitSet; + +import static net.minestom.server.network.NetworkBuffer.LONG_ARRAY; + +public record FilterMask(@NotNull Type type, @NotNull BitSet mask) implements NetworkBuffer.Writer { + public FilterMask(@NotNull NetworkBuffer reader) { + this(reader.readEnum(Type.class), BitSet.valueOf(reader.read(LONG_ARRAY))); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeEnum(Type.class, type); + if (type == Type.PARTIALLY_FILTERED) { + writer.write(LONG_ARRAY, mask.toLongArray()); + } + } + + public enum Type { + PASS_THROUGH, + FULLY_FILTERED, + PARTIALLY_FILTERED + } +} diff --git a/src/main/java/net/minestom/server/crypto/LastSeenMessages.java b/src/main/java/net/minestom/server/crypto/LastSeenMessages.java index e6508888d..11c123813 100644 --- a/src/main/java/net/minestom/server/crypto/LastSeenMessages.java +++ b/src/main/java/net/minestom/server/crypto/LastSeenMessages.java @@ -2,45 +2,47 @@ package net.minestom.server.crypto; import net.minestom.server.network.NetworkBuffer; import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; +import java.util.BitSet; import java.util.List; -import java.util.UUID; -public record LastSeenMessages(@NotNull List<@NotNull Entry> entries) implements NetworkBuffer.Writer { +import static net.minestom.server.network.NetworkBuffer.VAR_INT; + +public record LastSeenMessages(@NotNull List<@NotNull MessageSignature> entries) implements NetworkBuffer.Writer { public LastSeenMessages { entries = List.copyOf(entries); } public LastSeenMessages(@NotNull NetworkBuffer reader) { - this(reader.readCollection(Entry::new)); + this(reader.readCollection(MessageSignature::new)); } @Override public void write(@NotNull NetworkBuffer writer) { } - public record Entry(UUID from, MessageSignature lastSignature) implements NetworkBuffer.Writer { - public Entry(@NotNull NetworkBuffer reader) { - this(reader.read(NetworkBuffer.UUID), new MessageSignature(reader)); + public record Packed(@NotNull List entries) implements NetworkBuffer.Writer { + public static final Packed EMPTY = new Packed(List.of()); + + public Packed(@NotNull NetworkBuffer reader) { + this(reader.readCollection(MessageSignature.Packed::new)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(NetworkBuffer.UUID, from); - writer.write(lastSignature); + writer.writeCollection(entries); } } - public record Update(LastSeenMessages lastSeen, @Nullable Entry lastReceived) implements NetworkBuffer.Writer { + public record Update(int offset, @NotNull BitSet acknowledged) implements NetworkBuffer.Writer { public Update(@NotNull NetworkBuffer reader) { - this(new LastSeenMessages(reader), reader.readOptional(Entry::new)); + this(reader.read(VAR_INT), reader.readFixedBitSet(20)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(lastSeen); - writer.writeOptional(lastReceived); + writer.write(VAR_INT, offset); + writer.writeFixedBitSet(acknowledged, 20); } } } diff --git a/src/main/java/net/minestom/server/crypto/MessageSignature.java b/src/main/java/net/minestom/server/crypto/MessageSignature.java index d76e3b8e6..73885900c 100644 --- a/src/main/java/net/minestom/server/crypto/MessageSignature.java +++ b/src/main/java/net/minestom/server/crypto/MessageSignature.java @@ -2,16 +2,45 @@ package net.minestom.server.crypto; import net.minestom.server.network.NetworkBuffer; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.UnknownNullability; -import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; +import static net.minestom.server.network.NetworkBuffer.RAW_BYTES; +import static net.minestom.server.network.NetworkBuffer.VAR_INT; public record MessageSignature(byte @NotNull [] signature) implements NetworkBuffer.Writer { + public MessageSignature { + if (signature.length != 256) { + throw new IllegalArgumentException("Signature must be 256 bytes long"); + } + } + public MessageSignature(@NotNull NetworkBuffer reader) { - this(reader.read(BYTE_ARRAY)); + this(reader.readBytes(256)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(BYTE_ARRAY, signature); + writer.write(RAW_BYTES, signature); + } + + public record Packed(int id, @UnknownNullability MessageSignature fullSignature) implements NetworkBuffer.Writer { + public Packed(@NotNull NetworkBuffer reader) { + this(read(reader)); + } + + private Packed(@NotNull Packed packed) { + this(packed.id, packed.fullSignature); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(VAR_INT, id + 1); + if (id == 0) writer.write(fullSignature); + } + + private static Packed read(NetworkBuffer reader) { + final int id = reader.read(VAR_INT) - 1; + return new Packed(id, id == -1 ? new MessageSignature(reader) : null); + } } } diff --git a/src/main/java/net/minestom/server/crypto/SignedMessageBody.java b/src/main/java/net/minestom/server/crypto/SignedMessageBody.java new file mode 100644 index 000000000..fc1372280 --- /dev/null +++ b/src/main/java/net/minestom/server/crypto/SignedMessageBody.java @@ -0,0 +1,31 @@ +package net.minestom.server.crypto; + +import net.minestom.server.network.NetworkBuffer; +import org.jetbrains.annotations.NotNull; + +import java.time.Instant; + +public final class SignedMessageBody { + + public record Packed(@NotNull String content, @NotNull Instant timeStamp, long salt, + LastSeenMessages.@NotNull Packed lastSeen) implements NetworkBuffer.Writer { + public Packed { + if (content.length() > 256) { + throw new IllegalArgumentException("Message content too long"); + } + } + + public Packed(@NotNull NetworkBuffer reader) { + this(reader.read(NetworkBuffer.STRING), Instant.ofEpochMilli(reader.read(NetworkBuffer.LONG)), + reader.read(NetworkBuffer.LONG), new LastSeenMessages.Packed(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(NetworkBuffer.STRING, content); + writer.write(NetworkBuffer.LONG, timeStamp.toEpochMilli()); + writer.write(NetworkBuffer.LONG, salt); + writer.write(lastSeen); + } + } +} diff --git a/src/main/java/net/minestom/server/entity/LivingEntity.java b/src/main/java/net/minestom/server/entity/LivingEntity.java index 3559f514f..2ad606804 100644 --- a/src/main/java/net/minestom/server/entity/LivingEntity.java +++ b/src/main/java/net/minestom/server/entity/LivingEntity.java @@ -374,8 +374,8 @@ public class LivingEntity extends Entity implements EquipmentHandler { // TODO: separate living entity categories soundCategory = Source.HOSTILE; } - sendPacketToViewersAndSelf(new SoundEffectPacket(sound, soundCategory, - getPosition(), 1.0f, 1.0f)); + sendPacketToViewersAndSelf(new SoundEffectPacket(sound, null, soundCategory, + getPosition(), 1.0f, 1.0f, 0)); } }); diff --git a/src/main/java/net/minestom/server/entity/Metadata.java b/src/main/java/net/minestom/server/entity/Metadata.java index b70d0d253..6cfffe1af 100644 --- a/src/main/java/net/minestom/server/entity/Metadata.java +++ b/src/main/java/net/minestom/server/entity/Metadata.java @@ -28,6 +28,10 @@ public final class Metadata { return new MetadataImpl.EntryImpl<>(TYPE_VARINT, value, NetworkBuffer.VAR_INT); } + public static Entry Long(long value) { + return new MetadataImpl.EntryImpl<>(TYPE_LONG, value, NetworkBuffer.LONG); + } + public static Entry Float(float value) { return new MetadataImpl.EntryImpl<>(TYPE_FLOAT, value, NetworkBuffer.FLOAT); } @@ -97,23 +101,24 @@ public final class Metadata { public static final byte TYPE_BYTE = 0; public static final byte TYPE_VARINT = 1; - public static final byte TYPE_FLOAT = 2; - public static final byte TYPE_STRING = 3; - public static final byte TYPE_CHAT = 4; - public static final byte TYPE_OPTCHAT = 5; - public static final byte TYPE_SLOT = 6; - public static final byte TYPE_BOOLEAN = 7; - public static final byte TYPE_ROTATION = 8; - public static final byte TYPE_POSITION = 9; - public static final byte TYPE_OPTPOSITION = 10; - public static final byte TYPE_DIRECTION = 11; - public static final byte TYPE_OPTUUID = 12; - public static final byte TYPE_OPTBLOCKID = 13; - public static final byte TYPE_NBT = 14; - public static final byte TYPE_PARTICLE = 15; - public static final byte TYPE_VILLAGERDATA = 16; - public static final byte TYPE_OPTVARINT = 17; - public static final byte TYPE_POSE = 18; + public static final byte TYPE_LONG = 2; + public static final byte TYPE_FLOAT = 3; + public static final byte TYPE_STRING = 4; + public static final byte TYPE_CHAT = 5; + public static final byte TYPE_OPTCHAT = 6; + public static final byte TYPE_SLOT = 7; + public static final byte TYPE_BOOLEAN = 8; + public static final byte TYPE_ROTATION = 9; + public static final byte TYPE_POSITION = 10; + public static final byte TYPE_OPTPOSITION = 11; + public static final byte TYPE_DIRECTION = 12; + public static final byte TYPE_OPTUUID = 13; + public static final byte TYPE_OPTBLOCKID = 14; + public static final byte TYPE_NBT = 15; + public static final byte TYPE_PARTICLE = 16; + public static final byte TYPE_VILLAGERDATA = 17; + public static final byte TYPE_OPTVARINT = 18; + public static final byte TYPE_POSE = 19; private static final VarHandle NOTIFIED_CHANGES; diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index b3972f5ad..4f1bdc4eb 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -452,7 +452,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, refreshHealth(); sendPacket(new RespawnPacket(getDimensionType().toString(), getDimensionType().getName().asString(), - 0, gameMode, gameMode, false, levelFlat, true, deathLocation)); + 0, gameMode, gameMode, false, levelFlat, true, deathLocation)); PlayerRespawnEvent respawnEvent = new PlayerRespawnEvent(this); EventDispatcher.call(respawnEvent); @@ -944,8 +944,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, */ public void setDisplayName(@Nullable Component displayName) { this.displayName = displayName; - PacketUtils.broadcastPacket(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - new PlayerInfoPacket.UpdateDisplayName(getUuid(), displayName))); + PacketUtils.broadcastPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, infoEntry())); } /** @@ -973,8 +972,8 @@ public class Player extends LivingEntity implements CommandSender, Localizable, DestroyEntitiesPacket destroyEntitiesPacket = new DestroyEntitiesPacket(getEntityId()); - final PlayerInfoPacket removePlayerPacket = getRemovePlayerToList(); - final PlayerInfoPacket addPlayerPacket = getAddPlayerToList(); + final PlayerInfoRemovePacket removePlayerPacket = getRemovePlayerToList(); + final PlayerInfoUpdatePacket addPlayerPacket = getAddPlayerToList(); RespawnPacket respawnPacket = new RespawnPacket(getDimensionType().toString(), getDimensionType().getName().asString(), 0, gameMode, gameMode, false, levelFlat, true, deathLocation); @@ -1308,8 +1307,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, // Condition to prevent sending the packets before spawning the player if (isActive()) { sendPacket(new ChangeGameStatePacket(ChangeGameStatePacket.Reason.CHANGE_GAMEMODE, gameMode.id())); - PacketUtils.broadcastPacket(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE, - new PlayerInfoPacket.UpdateGameMode(getUuid(), gameMode))); + PacketUtils.broadcastPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, infoEntry())); } // The client updates their abilities based on the GameMode as follows @@ -1809,8 +1807,7 @@ public class Player extends LivingEntity implements CommandSender, Localizable, */ public void refreshLatency(int latency) { this.latency = latency; - PacketUtils.broadcastPacket(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY, - new PlayerInfoPacket.UpdateLatency(getUuid(), latency))); + PacketUtils.broadcastPacket(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, infoEntry())); } public void refreshOnGround(boolean onGround) { @@ -1928,24 +1925,29 @@ public class Player extends LivingEntity implements CommandSender, Localizable, /** * Gets the packet to add the player from the tab-list. * - * @return a {@link PlayerInfoPacket} to add the player + * @return a {@link PlayerInfoUpdatePacket} to add the player */ - protected @NotNull PlayerInfoPacket getAddPlayerToList() { - final PlayerSkin skin = this.skin; - List prop = skin != null ? - List.of(new PlayerInfoPacket.AddPlayer.Property("textures", skin.textures(), skin.signature())) : - List.of(); - return new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER, - new PlayerInfoPacket.AddPlayer(getUuid(), getUsername(), prop, getGameMode(), getLatency(), displayName, null)); + protected @NotNull PlayerInfoUpdatePacket getAddPlayerToList() { + return new PlayerInfoUpdatePacket(EnumSet.of(PlayerInfoUpdatePacket.Action.ADD_PLAYER, PlayerInfoUpdatePacket.Action.UPDATE_LISTED), + List.of(infoEntry())); } /** * Gets the packet to remove the player from the tab-list. * - * @return a {@link PlayerInfoPacket} to remove the player + * @return a {@link PlayerInfoRemovePacket} to remove the player */ - protected @NotNull PlayerInfoPacket getRemovePlayerToList() { - return new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER, new PlayerInfoPacket.RemovePlayer(getUuid())); + protected @NotNull PlayerInfoRemovePacket getRemovePlayerToList() { + return new PlayerInfoRemovePacket(getUuid()); + } + + private PlayerInfoUpdatePacket.Entry infoEntry() { + final PlayerSkin skin = this.skin; + List prop = skin != null ? + List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())) : + List.of(); + return new PlayerInfoUpdatePacket.Entry(getUuid(), getUsername(), prop, + true, getLatency(), getGameMode(), displayName, null); } /** diff --git a/src/main/java/net/minestom/server/network/NetworkBuffer.java b/src/main/java/net/minestom/server/network/NetworkBuffer.java index 11110d8f2..6ff7431e5 100644 --- a/src/main/java/net/minestom/server/network/NetworkBuffer.java +++ b/src/main/java/net/minestom/server/network/NetworkBuffer.java @@ -16,9 +16,7 @@ import org.jglrxavpok.hephaistos.nbt.NBTWriter; import java.nio.ByteBuffer; import java.nio.ByteOrder; -import java.util.Collection; -import java.util.List; -import java.util.UUID; +import java.util.*; import java.util.function.BiConsumer; import java.util.function.Consumer; import java.util.function.Function; @@ -204,6 +202,43 @@ public final class NetworkBuffer { return enumClass.getEnumConstants()[read(VAR_INT)]; } + public > void writeEnumSet(EnumSet enumSet, Class enumType) { + final E[] values = enumType.getEnumConstants(); + BitSet bitSet = new BitSet(values.length); + for (int i = 0; i < values.length; ++i) { + bitSet.set(i, enumSet.contains(values[i])); + } + writeFixedBitSet(bitSet, values.length); + } + + public > @NotNull EnumSet readEnumSet(Class enumType) { + final E[] values = enumType.getEnumConstants(); + BitSet bitSet = readFixedBitSet(values.length); + EnumSet enumSet = EnumSet.noneOf(enumType); + for (int i = 0; i < values.length; ++i) { + if (bitSet.get(i)) { + enumSet.add(values[i]); + } + } + return enumSet; + } + + public void writeFixedBitSet(BitSet set, int length) { + final int setLength = set.length(); + if (setLength > length) { + throw new IllegalArgumentException("BitSet is larger than expected size (" + setLength + ">" + length + ")"); + } else { + final byte[] array = set.toByteArray(); + write(RAW_BYTES, array); + } + } + + @NotNull + public BitSet readFixedBitSet(int length) { + final byte[] array = readBytes((length + 7) / 8); + return BitSet.valueOf(array); + } + public byte[] readBytes(int length) { byte[] bytes = new byte[length]; nioBuffer.get(readIndex, bytes, 0, length); diff --git a/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java b/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java index 25fd1a4c1..e381d1a63 100644 --- a/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java +++ b/src/main/java/net/minestom/server/network/packet/client/ClientPacketsHandler.java @@ -58,33 +58,33 @@ public sealed class ClientPacketsHandler permits ClientPacketsHandler.Status, Cl register(0x03, ClientChatAckPacket::new); register(0x04, ClientCommandChatPacket::new); register(0x05, ClientChatMessagePacket::new); - register(0x06, ClientChatPreviewPacket::new); - register(0x07, ClientStatusPacket::new); - register(0x08, ClientSettingsPacket::new); - register(0x09, ClientTabCompletePacket::new); - register(0x0A, ClientClickWindowButtonPacket::new); - register(0x0B, ClientClickWindowPacket::new); - register(0x0C, ClientCloseWindowPacket::new); - register(0x0D, ClientPluginMessagePacket::new); - register(0x0E, ClientEditBookPacket::new); - register(0x0F, ClientQueryEntityNbtPacket::new); - register(0x10, ClientInteractEntityPacket::new); - register(0x11, ClientGenerateStructurePacket::new); - register(0x12, ClientKeepAlivePacket::new); + register(0x06, ClientStatusPacket::new); + register(0x07, ClientSettingsPacket::new); + register(0x08, ClientTabCompletePacket::new); + register(0x09, ClientClickWindowButtonPacket::new); + register(0x0A, ClientClickWindowPacket::new); + register(0x0B, ClientCloseWindowPacket::new); + register(0x0C, ClientPluginMessagePacket::new); + register(0x0D, ClientEditBookPacket::new); + register(0x0E, ClientQueryEntityNbtPacket::new); + register(0x0F, ClientInteractEntityPacket::new); + register(0x10, ClientGenerateStructurePacket::new); + register(0x11, ClientKeepAlivePacket::new); // 0x12 packet not used server-side - register(0x14, ClientPlayerPositionPacket::new); - register(0x15, ClientPlayerPositionAndRotationPacket::new); - register(0x16, ClientPlayerRotationPacket::new); - register(0x17, ClientPlayerPacket::new); - register(0x18, ClientVehicleMovePacket::new); - register(0x19, ClientSteerBoatPacket::new); - register(0x1A, ClientPickItemPacket::new); - register(0x1B, ClientCraftRecipeRequest::new); - register(0x1C, ClientPlayerAbilitiesPacket::new); - register(0x1D, ClientPlayerDiggingPacket::new); - register(0x1E, ClientEntityActionPacket::new); - register(0x1F, ClientSteerVehiclePacket::new); - register(0x20, ClientPongPacket::new); + register(0x13, ClientPlayerPositionPacket::new); + register(0x14, ClientPlayerPositionAndRotationPacket::new); + register(0x15, ClientPlayerRotationPacket::new); + register(0x16, ClientPlayerPacket::new); + register(0x17, ClientVehicleMovePacket::new); + register(0x18, ClientSteerBoatPacket::new); + register(0x19, ClientPickItemPacket::new); + register(0x1A, ClientCraftRecipeRequest::new); + register(0x1B, ClientPlayerAbilitiesPacket::new); + register(0x1C, ClientPlayerDiggingPacket::new); + register(0x1D, ClientEntityActionPacket::new); + register(0x1E, ClientSteerVehiclePacket::new); + register(0x1F, ClientPongPacket::new); + register(0x20, ClientChatSessionUpdatePacket::new); register(0x21, ClientSetRecipeBookStatePacket::new); register(0x22, ClientSetDisplayedRecipePacket::new); register(0x23, ClientNameItemPacket::new); 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 f622a16b7..eaf57a25c 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 @@ -3,18 +3,13 @@ package net.minestom.server.network.packet.client.login; import com.google.gson.Gson; import com.google.gson.JsonObject; import net.minestom.server.MinecraftServer; -import net.minestom.server.crypto.SaltSignaturePair; -import net.minestom.server.crypto.SignatureValidator; 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.PlayerConnection; import net.minestom.server.network.player.PlayerSocketConnection; -import net.minestom.server.utils.Either; -import net.minestom.server.utils.InterfaceUtils; import net.minestom.server.utils.async.AsyncUtils; -import net.minestom.server.utils.crypto.KeyUtils; import org.jetbrains.annotations.NotNull; import javax.crypto.SecretKey; @@ -28,14 +23,14 @@ import java.nio.charset.StandardCharsets; import java.util.Arrays; import java.util.UUID; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; public record EncryptionResponsePacket(byte[] sharedSecret, - Either nonceOrSignature) implements ClientPreplayPacket { + byte[] encryptedVerifyToken) implements ClientPreplayPacket { private static final Gson GSON = new Gson(); public EncryptionResponsePacket(@NotNull NetworkBuffer reader) { - this(reader.read(BYTE_ARRAY), reader.readEither(networkBuffer -> networkBuffer.read(BYTE_ARRAY), SaltSignaturePair::new)); + this(reader.read(BYTE_ARRAY), reader.read(BYTE_ARRAY)); } @Override @@ -50,15 +45,8 @@ public record EncryptionResponsePacket(byte[] sharedSecret, } final boolean hasPublicKey = connection.playerPublicKey() != null; - final boolean verificationFailed = nonceOrSignature.map( - nonce -> hasPublicKey || !Arrays.equals(socketConnection.getNonce(), - MojangCrypt.decryptUsingKey(MojangAuth.getKeyPair().getPrivate(), nonce)), - signature -> !hasPublicKey || !SignatureValidator - .from(connection.playerPublicKey().publicKey(), KeyUtils.SignatureAlgorithm.SHA256withRSA) - .validate(binaryWriter -> { - binaryWriter.write(RAW_BYTES, socketConnection.getNonce()); - binaryWriter.write(LONG, signature.salt()); - }, signature.signature())); + final boolean verificationFailed = hasPublicKey || !Arrays.equals(socketConnection.getNonce(), + MojangCrypt.decryptUsingKey(MojangAuth.getKeyPair().getPrivate(), encryptedVerifyToken)); if (verificationFailed) { MinecraftServer.LOGGER.error("Encryption failed for {}", loginUsername); @@ -111,8 +99,7 @@ public record EncryptionResponsePacket(byte[] sharedSecret, @Override public void write(@NotNull NetworkBuffer writer) { writer.write(BYTE_ARRAY, sharedSecret); - writer.writeEither(nonceOrSignature, (networkBuffer, bytes) -> networkBuffer.write(BYTE_ARRAY, bytes), - InterfaceUtils.flipBiConsumer(SaltSignaturePair::write)); + writer.write(BYTE_ARRAY, encryptedVerifyToken); } private SecretKey getSecretKey() { diff --git a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java index 07716f76c..239b68367 100644 --- a/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/login/LoginStartPacket.java @@ -2,9 +2,6 @@ package net.minestom.server.network.packet.client.login; import net.kyori.adventure.text.Component; import net.kyori.adventure.text.format.NamedTextColor; -import net.minestom.server.MinecraftServer; -import net.minestom.server.crypto.PlayerPublicKey; -import net.minestom.server.crypto.SignatureValidator; import net.minestom.server.extras.MojangAuth; import net.minestom.server.extras.bungee.BungeeCordProxy; import net.minestom.server.extras.velocity.VelocityProxy; @@ -19,45 +16,22 @@ import net.minestom.server.network.player.PlayerSocketConnection; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; -import java.time.Instant; import java.util.UUID; import java.util.concurrent.ThreadLocalRandom; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.STRING; +import static net.minestom.server.network.NetworkBuffer.UUID; public record LoginStartPacket(@NotNull String username, - @Nullable PlayerPublicKey publicKey, @Nullable UUID profileId) implements ClientPreplayPacket { private static final Component ALREADY_CONNECTED = Component.text("You are already on this server", NamedTextColor.RED); public LoginStartPacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), reader.readOptional(PlayerPublicKey::new), reader.readOptional(UUID)); + this(reader.read(STRING), reader.readOptional(UUID)); } @Override public void process(@NotNull PlayerConnection connection) { - // TODO use uuid - // TODO configurable check & messages - if (publicKey != null) { - if (!SignatureValidator.YGGDRASIL.validate(binaryWriter -> { - if (profileId != null) { - binaryWriter.write(LONG, profileId.getMostSignificantBits()); - binaryWriter.write(LONG, profileId.getLeastSignificantBits()); - } else { - MinecraftServer.LOGGER.warn("Profile ID was null for player {}, signature will not match!", username); - } - binaryWriter.write(LONG, publicKey.expiresAt().toEpochMilli()); - binaryWriter.write(RAW_BYTES, publicKey.publicKey().getEncoded()); - }, publicKey.signature())) { - connection.sendPacket(new LoginDisconnectPacket(Component.text("Invalid Profile Public Key!"))); - connection.disconnect(); - } - if (publicKey.expiresAt().isBefore(Instant.now())) { - connection.sendPacket(new LoginDisconnectPacket(Component.text("Expired Profile Public Key!"))); - connection.disconnect(); - } - connection.setPlayerPublicKey(publicKey); - } final boolean isSocketConnection = connection instanceof PlayerSocketConnection; // Proxy support (only for socket clients) and cache the login username if (isSocketConnection) { diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java index b5cc19c9e..9f448d037 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatAckPacket.java @@ -1,17 +1,18 @@ package net.minestom.server.network.packet.client.play; -import net.minestom.server.crypto.LastSeenMessages; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; -public record ClientChatAckPacket(@NotNull LastSeenMessages.Update update) implements ClientPacket { +import static net.minestom.server.network.NetworkBuffer.VAR_INT; + +public record ClientChatAckPacket(int offset) implements ClientPacket { public ClientChatAckPacket(@NotNull NetworkBuffer reader) { - this(new LastSeenMessages.Update(reader)); + this(reader.read(VAR_INT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(update); + writer.write(VAR_INT, offset); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java index 8f79ee147..3da9f83f4 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatMessagePacket.java @@ -1,28 +1,23 @@ package net.minestom.server.network.packet.client.play; -import net.minestom.server.crypto.LastSeenMessages; -import net.minestom.server.crypto.MessageSignature; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.Arrays; +import java.util.BitSet; import static net.minestom.server.network.NetworkBuffer.*; -public record ClientChatMessagePacket(@NotNull String message, - long timestamp, long salt, @NotNull MessageSignature signature, - boolean signedPreview, - @NotNull LastSeenMessages.Update lastSeenMessages) implements ClientPacket { - public ClientChatMessagePacket { - if (message.length() > 256) { - throw new IllegalArgumentException("Message cannot be more than 256 characters long."); - } - } +public record ClientChatMessagePacket(String message, long timestamp, + long salt, byte @Nullable [] signature, + int ackOffset, BitSet ackList) implements ClientPacket { public ClientChatMessagePacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), - reader.read(LONG), reader.read(LONG), new MessageSignature(reader), - reader.read(BOOLEAN), - new LastSeenMessages.Update(reader)); + this(reader.read(STRING), reader.read(LONG), + reader.read(LONG), reader.readOptional(r -> r.readBytes(256)), + reader.read(VAR_INT), BitSet.valueOf(reader.readBytes(3))); } @Override @@ -30,8 +25,8 @@ public record ClientChatMessagePacket(@NotNull String message, writer.write(STRING, message); writer.write(LONG, timestamp); writer.write(LONG, salt); - writer.write(signature); - writer.write(BOOLEAN, signedPreview); - writer.write(lastSeenMessages); + writer.writeOptional(BYTE_ARRAY, signature); + writer.write(VAR_INT, ackOffset); + writer.write(RAW_BYTES, Arrays.copyOf(ackList.toByteArray(), 3)); } } diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java deleted file mode 100644 index 9586976dc..000000000 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatPreviewPacket.java +++ /dev/null @@ -1,26 +0,0 @@ -package net.minestom.server.network.packet.client.play; - -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.client.ClientPacket; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.INT; -import static net.minestom.server.network.NetworkBuffer.STRING; - -public record ClientChatPreviewPacket(int queryId, @NotNull String query) implements ClientPacket { - public ClientChatPreviewPacket { - if (query.length() > 256) { - throw new IllegalArgumentException("Query length cannot be greater than 256"); - } - } - - public ClientChatPreviewPacket(@NotNull NetworkBuffer reader) { - this(reader.read(INT), reader.read(STRING)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(INT, queryId); - writer.write(STRING, query); - } -} diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java new file mode 100644 index 000000000..a18b33e9a --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientChatSessionUpdatePacket.java @@ -0,0 +1,17 @@ +package net.minestom.server.network.packet.client.play; + +import net.minestom.server.crypto.ChatSession; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.client.ClientPacket; +import org.jetbrains.annotations.NotNull; + +public record ClientChatSessionUpdatePacket(@NotNull ChatSession chatSession) implements ClientPacket { + public ClientChatSessionUpdatePacket(@NotNull NetworkBuffer reader) { + this(new ChatSession(reader)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(chatSession); + } +} diff --git a/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java b/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java index a9d481ae5..ef29ff546 100644 --- a/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java +++ b/src/main/java/net/minestom/server/network/packet/client/play/ClientCommandChatPacket.java @@ -6,11 +6,11 @@ import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.client.ClientPacket; import org.jetbrains.annotations.NotNull; -import static net.minestom.server.network.NetworkBuffer.*; +import static net.minestom.server.network.NetworkBuffer.LONG; +import static net.minestom.server.network.NetworkBuffer.STRING; public record ClientCommandChatPacket(@NotNull String message, long timestamp, long salt, @NotNull ArgumentSignatures signatures, - boolean signedPreview, LastSeenMessages.@NotNull Update lastSeenMessages) implements ClientPacket { public ClientCommandChatPacket { if (message.length() > 256) { @@ -20,7 +20,8 @@ public record ClientCommandChatPacket(@NotNull String message, long timestamp, public ClientCommandChatPacket(@NotNull NetworkBuffer reader) { this(reader.read(STRING), reader.read(LONG), - reader.read(LONG), new ArgumentSignatures(reader), reader.read(BOOLEAN), new LastSeenMessages.Update(reader)); + reader.read(LONG), new ArgumentSignatures(reader), + new LastSeenMessages.Update(reader)); } @Override @@ -29,7 +30,6 @@ public record ClientCommandChatPacket(@NotNull String message, long timestamp, writer.write(LONG, timestamp); writer.write(LONG, salt); writer.write(signatures); - writer.write(BOOLEAN, signedPreview); writer.write(lastSeenMessages); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java index fc4a119e5..8dc276767 100644 --- a/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java +++ b/src/main/java/net/minestom/server/network/packet/server/ServerPacketIdentifier.java @@ -23,7 +23,6 @@ public final class ServerPacketIdentifier { public static final int BLOCK_CHANGE = nextPlayId(); public static final int BOSS_BAR = nextPlayId(); public static final int SERVER_DIFFICULTY = nextPlayId(); - public static final int CHAT_PREVIEW = nextPlayId(); public static final int CLEAR_TITLES = nextPlayId(); public static final int TAB_COMPLETE = nextPlayId(); public static final int DECLARE_COMMANDS = nextPlayId(); @@ -34,9 +33,9 @@ public final class ServerPacketIdentifier { public static final int SET_COOLDOWN = nextPlayId(); public static final int CUSTOM_CHAT_COMPLETIONS = nextPlayId(); public static final int PLUGIN_MESSAGE = nextPlayId(); - public static final int NAMED_SOUND_EFFECT = nextPlayId(); public static final int DELETE_CHAT_MESSAGE = nextPlayId(); public static final int DISCONNECT = nextPlayId(); + public static final int DISGUISED_CHAT = nextPlayId(); public static final int ENTITY_STATUS = nextPlayId(); public static final int EXPLOSION = nextPlayId(); public static final int UNLOAD_CHUNK = nextPlayId(); @@ -61,12 +60,12 @@ public final class ServerPacketIdentifier { public static final int PING = nextPlayId(); public static final int CRAFT_RECIPE_RESPONSE = nextPlayId(); public static final int PLAYER_ABILITIES = nextPlayId(); - public static final int PLAYER_CHAT_HEADER = nextPlayId(); public static final int PLAYER_CHAT = nextPlayId(); public static final int END_COMBAT_EVENT = nextPlayId(); public static final int ENTER_COMBAT_EVENT = nextPlayId(); public static final int DEATH_COMBAT_EVENT = nextPlayId(); - public static final int PLAYER_INFO = nextPlayId(); + public static final int PLAYER_INFO_REMOVE = nextPlayId(); + public static final int PLAYER_INFO_UPDATE = nextPlayId(); public static final int FACE_PLAYER = nextPlayId(); public static final int PLAYER_POSITION_AND_LOOK = nextPlayId(); public static final int UNLOCK_RECIPES = nextPlayId(); @@ -89,7 +88,6 @@ public final class ServerPacketIdentifier { public static final int UPDATE_VIEW_POSITION = nextPlayId(); public static final int UPDATE_VIEW_DISTANCE = nextPlayId(); // Not used by the dedicated server public static final int SPAWN_POSITION = nextPlayId(); - public static final int SET_DISPLAY_CHAT_PREVIEW = nextPlayId(); public static final int DISPLAY_SCOREBOARD = nextPlayId(); public static final int ENTITY_METADATA = nextPlayId(); public static final int ATTACH_ENTITY = nextPlayId(); @@ -116,6 +114,7 @@ public final class ServerPacketIdentifier { public static final int ENTITY_TELEPORT = nextPlayId(); public static final int ADVANCEMENTS = nextPlayId(); public static final int ENTITY_PROPERTIES = nextPlayId(); + public static final int UPDATE_ENABLED_FEATURES = nextPlayId(); public static final int ENTITY_EFFECT = nextPlayId(); public static final int DECLARE_RECIPES = nextPlayId(); public static final int TAGS = nextPlayId(); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java deleted file mode 100644 index a197df2ac..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/ChatPreviewPacket.java +++ /dev/null @@ -1,42 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.kyori.adventure.text.Component; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.Collection; -import java.util.List; -import java.util.function.UnaryOperator; - -import static net.minestom.server.network.NetworkBuffer.*; - -public record ChatPreviewPacket(int queryId, @Nullable Component preview) implements ComponentHoldingServerPacket { - public ChatPreviewPacket(@NotNull NetworkBuffer reader) { - this(reader.read(INT), reader.readOptional(COMPONENT)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(INT, queryId); - writer.writeOptional(COMPONENT, preview); - } - - @Override - public int getId() { - return ServerPacketIdentifier.CHAT_PREVIEW; - } - - @Override - public @NotNull Collection components() { - return List.of(preview); - } - - @Override - public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return new ChatPreviewPacket(queryId, operator.apply(preview)); - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java index 6d93af459..1da9e5752 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/EntitySoundEffectPacket.java @@ -2,23 +2,86 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.sound.Sound; import net.minestom.server.adventure.AdventurePacketConvertor; +import net.minestom.server.coordinate.Point; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import net.minestom.server.sound.SoundEvent; +import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; -public record EntitySoundEffectPacket(int soundId, Sound.Source source, int entityId, - float volume, float pitch, long seed) implements ServerPacket { +public record EntitySoundEffectPacket( + // only one of soundEvent and soundName may be present + @Nullable SoundEvent soundEvent, + @Nullable String soundName, + @Nullable Float range, // Only allowed with soundName + @NotNull Sound.Source source, + int entityId, + float volume, + float pitch, + long seed +) implements ServerPacket { + + public EntitySoundEffectPacket { + Check.argCondition(soundEvent == null && soundName == null, "soundEvent and soundName cannot both be null"); + Check.argCondition(soundEvent != null && soundName != null, "soundEvent and soundName cannot both be present"); + } + + public EntitySoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Sound.Source source, + int entityId, float volume, float pitch, long seed) { + this(soundEvent, null, range, source, entityId, volume, pitch, seed); + } + + public EntitySoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Sound.Source source, + int entityId, float volume, float pitch, long seed) { + this(null, soundName, range, source, entityId, volume, pitch, seed); + } + public EntitySoundEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(VAR_INT), Sound.Source.values()[reader.read(VAR_INT)], reader.read(VAR_INT), - reader.read(FLOAT), reader.read(FLOAT), reader.read(LONG)); + this(fromReader(reader)); + } + + private EntitySoundEffectPacket(@NotNull EntitySoundEffectPacket packet) { + this(packet.soundEvent, packet.soundName, packet.range, packet.source, packet.entityId, packet.volume, packet.pitch, packet.seed); + } + + private static @NotNull EntitySoundEffectPacket fromReader(@NotNull NetworkBuffer reader) { + int soundId = reader.read(VAR_INT); + SoundEvent soundEvent; + String soundName; + Float range = null; + if (soundId == 0) { + soundEvent = null; + soundName = reader.read(STRING); + range = reader.readOptional(FLOAT); + } else { + soundEvent = SoundEvent.fromId(soundId - 1); + soundName = null; + } + return new EntitySoundEffectPacket( + soundEvent, + soundName, + range, + reader.readEnum(Sound.Source.class), + reader.read(VAR_INT), + reader.read(FLOAT), + reader.read(FLOAT), + reader.read(LONG) + ); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, soundId); + if (soundEvent != null) { + writer.write(VAR_INT, soundEvent.id() + 1); + } else { + writer.write(VAR_INT, 0); + writer.write(STRING, soundName); + writer.writeOptional(FLOAT, null); + } writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(VAR_INT, entityId); writer.write(FLOAT, volume); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java index 3ff4ddaea..145d7ced9 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ExplosionPacket.java @@ -7,19 +7,19 @@ import org.jetbrains.annotations.NotNull; import static net.minestom.server.network.NetworkBuffer.*; -public record ExplosionPacket(float x, float y, float z, float radius, byte @NotNull [] records, +public record ExplosionPacket(double x, double y, double z, float radius, byte @NotNull [] records, float playerMotionX, float playerMotionY, float playerMotionZ) implements ServerPacket { public ExplosionPacket(@NotNull NetworkBuffer reader) { - this(reader.read(FLOAT), reader.read(FLOAT), reader.read(FLOAT), + this(reader.read(DOUBLE), reader.read(DOUBLE), reader.read(DOUBLE), reader.read(FLOAT), reader.readBytes(reader.read(VAR_INT) * 3), reader.read(FLOAT), reader.read(FLOAT), reader.read(FLOAT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(FLOAT, x); - writer.write(FLOAT, y); - writer.write(FLOAT, z); + writer.write(DOUBLE, x); + writer.write(DOUBLE, y); + writer.write(DOUBLE, z); writer.write(FLOAT, radius); writer.write(VAR_INT, records.length / 3); // each record is 3 bytes long writer.write(RAW_BYTES, records); diff --git a/src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java deleted file mode 100644 index 7ceaea8ec..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/NamedSoundEffectPacket.java +++ /dev/null @@ -1,36 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.kyori.adventure.sound.Sound.Source; -import net.minestom.server.adventure.AdventurePacketConvertor; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.*; - -public record NamedSoundEffectPacket(String soundName, Source source, int x, int y, int z, - float volume, float pitch, long seed) implements ServerPacket { - public NamedSoundEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), Source.values()[reader.read(VAR_INT)], - reader.read(INT) / 8, reader.read(INT) / 8, reader.read(INT) / 8, - reader.read(FLOAT), reader.read(FLOAT), reader.read(LONG)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, soundName); - writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); - writer.write(INT, x * 8); - writer.write(INT, y * 8); - writer.write(INT, z * 8); - writer.write(FLOAT, volume); - writer.write(FLOAT, pitch); - writer.write(LONG, seed); - } - - @Override - public int getId() { - return ServerPacketIdentifier.NAMED_SOUND_EFFECT; - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java deleted file mode 100644 index 2a13bab72..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatHeaderPacket.java +++ /dev/null @@ -1,29 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.minestom.server.crypto.MessageSignature; -import net.minestom.server.crypto.SignedMessageHeader; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.BYTE_ARRAY; - -public record PlayerChatHeaderPacket(@NotNull SignedMessageHeader messageHeader, @NotNull MessageSignature signature, - byte[] bodyDigest) implements ServerPacket { - public PlayerChatHeaderPacket(@NotNull NetworkBuffer reader) { - this(new SignedMessageHeader(reader), new MessageSignature(reader), reader.read(BYTE_ARRAY)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(messageHeader); - writer.write(signature); - writer.write(BYTE_ARRAY, bodyDigest); - } - - @Override - public int getId() { - return ServerPacketIdentifier.PLAYER_CHAT_HEADER; - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java index 143867aef..309eac8df 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerChatMessagePacket.java @@ -1,7 +1,8 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; -import net.minestom.server.crypto.MessageSignature; +import net.minestom.server.crypto.FilterMask; +import net.minestom.server.crypto.SignedMessageBody; import net.minestom.server.network.NetworkBuffer; import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; import net.minestom.server.network.packet.server.ServerPacket; @@ -9,6 +10,7 @@ import net.minestom.server.network.packet.server.ServerPacketIdentifier; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.ArrayList; import java.util.Collection; import java.util.List; import java.util.UUID; @@ -19,26 +21,30 @@ import static net.minestom.server.network.NetworkBuffer.*; /** * Represents an outgoing chat message packet. */ -public record PlayerChatMessagePacket(@NotNull Component signedContent, @Nullable Component unsignedContent, - int type, @NotNull UUID uuid, - @NotNull Component displayName, @Nullable Component teamDisplayName, - @NotNull MessageSignature signature) implements ComponentHoldingServerPacket { +public record PlayerChatMessagePacket(UUID sender, int index, byte @Nullable [] signature, + SignedMessageBody.@NotNull Packed messageBody, + @Nullable Component unsignedContent, FilterMask filterMask, + int msgTypeId, Component msgTypeName, + @Nullable Component msgTypeTarget) implements ComponentHoldingServerPacket { public PlayerChatMessagePacket(@NotNull NetworkBuffer reader) { - this(reader.read(COMPONENT), reader.readOptional(COMPONENT), - reader.read(VAR_INT), reader.read(NetworkBuffer.UUID), - reader.read(COMPONENT), reader.readOptional(COMPONENT), - new MessageSignature(reader)); + this(reader.read(UUID), reader.read(VAR_INT), reader.readOptional(r -> r.readBytes(256)), + new SignedMessageBody.Packed(reader), + reader.readOptional(COMPONENT), new FilterMask(reader), + reader.read(VAR_INT), reader.read(COMPONENT), + reader.readOptional(COMPONENT)); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(COMPONENT, signedContent); + writer.write(UUID, sender); + writer.write(VAR_INT, index); + writer.writeOptional(RAW_BYTES, signature); + writer.write(messageBody); writer.writeOptional(COMPONENT, unsignedContent); - writer.write(VAR_INT, type); - writer.write(UUID, uuid); - writer.write(COMPONENT, displayName); - writer.writeOptional(COMPONENT, teamDisplayName); - writer.write(signature); + writer.write(filterMask); + writer.write(VAR_INT, msgTypeId); + writer.write(COMPONENT, msgTypeName); + writer.writeOptional(COMPONENT, msgTypeTarget); } @Override @@ -48,12 +54,19 @@ public record PlayerChatMessagePacket(@NotNull Component signedContent, @Nullabl @Override public @NotNull Collection components() { - return List.of(signedContent); + final ArrayList list = new ArrayList<>(); + list.add(msgTypeName); + if (unsignedContent != null) list.add(unsignedContent); + if (msgTypeTarget != null) list.add(msgTypeTarget); + return List.copyOf(list); } @Override public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return new PlayerChatMessagePacket(signedContent, unsignedContent, type, - uuid, displayName, teamDisplayName, signature); + return new PlayerChatMessagePacket(sender, index, signature, + messageBody, + operator.apply(unsignedContent), filterMask, + msgTypeId, operator.apply(msgTypeName), + operator.apply(msgTypeTarget)); } } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java deleted file mode 100644 index 5e21cc4e8..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoPacket.java +++ /dev/null @@ -1,242 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.kyori.adventure.text.Component; -import net.minestom.server.adventure.ComponentHolder; -import net.minestom.server.crypto.PlayerPublicKey; -import net.minestom.server.entity.GameMode; -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ComponentHoldingServerPacket; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; -import org.jetbrains.annotations.Nullable; - -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.UUID; -import java.util.function.UnaryOperator; - -import static net.minestom.server.network.NetworkBuffer.*; - -public record PlayerInfoPacket(@NotNull Action action, - @NotNull List entries) implements ComponentHoldingServerPacket { - public PlayerInfoPacket { - entries = List.copyOf(entries); - for (Entry entry : entries) { - if (!entry.getClass().equals(action.getClazz())) - throw new IllegalArgumentException("Invalid entry class for action " + action); - } - } - - public PlayerInfoPacket(@NotNull Action action, @NotNull Entry entry) { - this(action, List.of(entry)); - } - - public PlayerInfoPacket(@NotNull NetworkBuffer reader) { - this(read(reader)); - } - - private PlayerInfoPacket(PlayerInfoPacket packet) { - this(packet.action, packet.entries); - } - - private static PlayerInfoPacket read(@NotNull NetworkBuffer reader) { - var action = Action.values()[reader.read(VAR_INT)]; - final int playerInfoCount = reader.read(VAR_INT); - List entries = new ArrayList<>(playerInfoCount); - for (int i = 0; i < playerInfoCount; i++) { - final UUID uuid = reader.read(UUID); - entries.add(switch (action) { - case ADD_PLAYER -> new AddPlayer(uuid, reader); - case UPDATE_GAMEMODE -> new UpdateGameMode(uuid, reader); - case UPDATE_LATENCY -> new UpdateLatency(uuid, reader); - case UPDATE_DISPLAY_NAME -> new UpdateDisplayName(uuid, reader); - case REMOVE_PLAYER -> new RemovePlayer(uuid); - }); - } - return new PlayerInfoPacket(action, entries); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, action.ordinal()); - writer.writeCollection(entries, (w, entry) -> { - w.write(UUID, entry.uuid()); - entry.write(w); - }); - } - - @Override - public int getId() { - return ServerPacketIdentifier.PLAYER_INFO; - } - - @Override - public @NotNull Collection components() { - switch (this.action) { - case ADD_PLAYER, UPDATE_DISPLAY_NAME -> { - List components = new ArrayList<>(); - for (Entry entry : entries) { - if (entry instanceof ComponentHolder) { - components.addAll(((ComponentHolder) entry).components()); - } - } - return components; - } - default -> { - return List.of(); - } - } - } - - @Override - public @NotNull ServerPacket copyWithOperator(@NotNull UnaryOperator operator) { - return switch (action) { - case ADD_PLAYER, UPDATE_DISPLAY_NAME -> { - List entries = new ArrayList<>(this.entries.size()); - for (Entry entry : this.entries) { - if (entry instanceof ComponentHolder) { - entries.add(((ComponentHolder) entry).copyWithOperator(operator)); - } else { - entries.add(entry); - } - } - yield new PlayerInfoPacket(action, entries); - } - default -> this; - }; - } - - public enum Action { - ADD_PLAYER(AddPlayer.class), - UPDATE_GAMEMODE(UpdateGameMode.class), - UPDATE_LATENCY(UpdateLatency.class), - UPDATE_DISPLAY_NAME(UpdateDisplayName.class), - REMOVE_PLAYER(RemovePlayer.class); - - private final Class clazz; - - Action(Class clazz) { - this.clazz = clazz; - } - - @NotNull - public Class getClazz() { - return clazz; - } - } - - public sealed interface Entry extends NetworkBuffer.Writer - permits AddPlayer, UpdateGameMode, UpdateLatency, UpdateDisplayName, RemovePlayer { - UUID uuid(); - } - - public record AddPlayer(UUID uuid, String name, List properties, GameMode gameMode, int ping, - @Nullable Component displayName, - @Nullable PlayerPublicKey playerPublicKey) implements Entry, ComponentHolder { - public AddPlayer { - properties = List.copyOf(properties); - } - - public AddPlayer(UUID uuid, NetworkBuffer reader) { - this(uuid, reader.read(STRING), - reader.readCollection(Property::new), - GameMode.values()[reader.read(VAR_INT)], reader.read(VAR_INT), - reader.read(BOOLEAN) ? reader.read(COMPONENT) : null, - reader.read(BOOLEAN) ? new PlayerPublicKey(reader) : null); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, name); - writer.writeCollection(properties); - writer.write(VAR_INT, (int) gameMode.id()); - writer.write(VAR_INT, ping); - writer.writeOptional(COMPONENT, displayName); - writer.writeOptional(playerPublicKey); - } - - @Override - public @NotNull Collection components() { - return displayName != null ? List.of(displayName) : List.of(); - } - - @Override - public @NotNull AddPlayer copyWithOperator(@NotNull UnaryOperator operator) { - return displayName != null ? - new AddPlayer(uuid, name, properties, gameMode, ping, operator.apply(displayName), playerPublicKey) : this; - } - - public record Property(@NotNull String name, @NotNull String value, - @Nullable String signature) implements NetworkBuffer.Writer { - public Property(String name, String value) { - this(name, value, null); - } - - public Property(@NotNull NetworkBuffer reader) { - this(reader.read(STRING), reader.read(STRING), - reader.read(BOOLEAN) ? reader.read(STRING) : null); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(STRING, name); - writer.write(STRING, value); - writer.write(BOOLEAN, signature != null); - if (signature != null) writer.write(STRING, signature); - } - } - } - - public record UpdateGameMode(UUID uuid, GameMode gameMode) implements Entry { - public UpdateGameMode(UUID uuid, NetworkBuffer reader) { - this(uuid, GameMode.fromId(reader.read(VAR_INT).byteValue())); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, (int) gameMode.id()); - } - } - - public record UpdateLatency(UUID uuid, int ping) implements Entry { - public UpdateLatency(UUID uuid, NetworkBuffer reader) { - this(uuid, reader.read(VAR_INT)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, ping); - } - } - - public record UpdateDisplayName(@NotNull UUID uuid, - @Nullable Component displayName) implements Entry, ComponentHolder { - public UpdateDisplayName(UUID uuid, NetworkBuffer reader) { - this(uuid, reader.read(BOOLEAN) ? reader.read(COMPONENT) : null); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(BOOLEAN, displayName != null); - if (displayName != null) writer.write(COMPONENT, displayName); - } - - @Override - public @NotNull Collection components() { - return displayName != null ? List.of(displayName) : List.of(); - } - - @Override - public @NotNull UpdateDisplayName copyWithOperator(@NotNull UnaryOperator operator) { - return displayName != null ? new UpdateDisplayName(uuid, operator.apply(displayName)) : this; - } - } - - public record RemovePlayer(@NotNull UUID uuid) implements Entry { - @Override - public void write(@NotNull NetworkBuffer writer) { - } - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java new file mode 100644 index 000000000..3eb2d15db --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoRemovePacket.java @@ -0,0 +1,33 @@ +package net.minestom.server.network.packet.server.play; + +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; + +import java.util.List; +import java.util.UUID; + +public record PlayerInfoRemovePacket(@NotNull List<@NotNull UUID> uuids) implements ServerPacket { + public PlayerInfoRemovePacket(@NotNull UUID uuid) { + this(List.of(uuid)); + } + + public PlayerInfoRemovePacket { + uuids = List.copyOf(uuids); + } + + public PlayerInfoRemovePacket(@NotNull NetworkBuffer reader) { + this(reader.readCollection(NetworkBuffer.UUID)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeCollection(NetworkBuffer.UUID, uuids); + } + + @Override + public int getId() { + return ServerPacketIdentifier.PLAYER_INFO_REMOVE; + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java new file mode 100644 index 000000000..1d03a5470 --- /dev/null +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerInfoUpdatePacket.java @@ -0,0 +1,154 @@ +package net.minestom.server.network.packet.server.play; + +import net.kyori.adventure.text.Component; +import net.minestom.server.crypto.ChatSession; +import net.minestom.server.entity.GameMode; +import net.minestom.server.network.NetworkBuffer; +import net.minestom.server.network.packet.server.ServerPacket; +import net.minestom.server.network.packet.server.ServerPacketIdentifier; +import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; + +import java.util.EnumSet; +import java.util.List; +import java.util.Objects; +import java.util.UUID; + +import static net.minestom.server.network.NetworkBuffer.*; + +public final class PlayerInfoUpdatePacket implements ServerPacket { + private final @NotNull EnumSet<@NotNull Action> actions; + private final @NotNull List<@NotNull Entry> entries; + + public PlayerInfoUpdatePacket(@NotNull EnumSet<@NotNull Action> actions, @NotNull List<@NotNull Entry> entries) { + this.actions = EnumSet.copyOf(actions); + this.entries = List.copyOf(entries); + } + + public PlayerInfoUpdatePacket(@NotNull Action action, @NotNull Entry entry) { + this.actions = EnumSet.of(action); + this.entries = List.of(entry); + } + + public PlayerInfoUpdatePacket(@NotNull NetworkBuffer reader) { + this.actions = reader.readEnumSet(Action.class); + this.entries = reader.readCollection(buffer -> { + UUID uuid = buffer.read(NetworkBuffer.UUID); + String username = ""; + List properties = List.of(); + boolean listed = false; + int latency = 0; + GameMode gameMode = GameMode.SURVIVAL; + Component displayName = null; + ChatSession chatSession = null; + for (Action action : actions) { + switch (action) { + case ADD_PLAYER -> { + username = reader.read(STRING); + properties = reader.readCollection(Property::new); + } + case INITIALIZE_CHAT -> chatSession = new ChatSession(reader); + case UPDATE_GAME_MODE -> gameMode = reader.readEnum(GameMode.class); + case UPDATE_LISTED -> listed = reader.read(BOOLEAN); + case UPDATE_LATENCY -> latency = reader.read(VAR_INT); + case UPDATE_DISPLAY_NAME -> displayName = reader.read(COMPONENT); + } + } + return new Entry(uuid, username, properties, listed, latency, gameMode, displayName, chatSession); + }); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.writeEnumSet(actions, Action.class); + writer.writeCollection(entries, (buffer, entry) -> { + buffer.write(NetworkBuffer.UUID, entry.uuid); + for (Action action : actions) { + action.writer.write(buffer, entry); + } + }); + } + + @Override + public int getId() { + return ServerPacketIdentifier.PLAYER_INFO_UPDATE; + } + + public @NotNull EnumSet actions() { + return actions; + } + + public @NotNull List entries() { + return entries; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + PlayerInfoUpdatePacket that = (PlayerInfoUpdatePacket) o; + return actions.equals(that.actions) && entries.equals(that.entries); + } + + @Override + public int hashCode() { + return Objects.hash(actions, entries); + } + + @Override + public String toString() { + return "PlayerInfoUpdatePacket{" + + "actions=" + actions + + ", entries=" + entries + + '}'; + } + + public record Entry(UUID uuid, String username, List properties, + boolean listed, int latency, GameMode gameMode, + @Nullable Component displayName, @Nullable ChatSession chatSession) { + public Entry { + properties = List.copyOf(properties); + } + } + + public record Property(@NotNull String name, @NotNull String value, + @Nullable String signature) implements NetworkBuffer.Writer { + public Property(@NotNull String name, @NotNull String value) { + this(name, value, null); + } + + public Property(@NotNull NetworkBuffer reader) { + this(reader.read(STRING), reader.read(STRING), + reader.readOptional(STRING)); + } + + @Override + public void write(@NotNull NetworkBuffer writer) { + writer.write(STRING, name); + writer.write(STRING, value); + writer.writeOptional(STRING, signature); + } + } + + public enum Action { + ADD_PLAYER((writer, entry) -> { + writer.write(STRING, entry.username); + writer.writeCollection(entry.properties); + }), + INITIALIZE_CHAT((writer, entry) -> writer.writeOptional(entry.chatSession)), + UPDATE_GAME_MODE((writer, entry) -> writer.write(VAR_INT, entry.gameMode.ordinal())), + UPDATE_LISTED((writer, entry) -> writer.write(BOOLEAN, entry.listed)), + UPDATE_LATENCY((writer, entry) -> writer.write(VAR_INT, entry.latency)), + UPDATE_DISPLAY_NAME((writer, entry) -> writer.writeOptional(COMPONENT, entry.displayName)); + + final Writer writer; + + Action(Writer writer) { + this.writer = writer; + } + + interface Writer { + void write(NetworkBuffer writer, Entry entry); + } + } +} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java index 35c7ad6d7..797185444 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/ServerDataPacket.java @@ -10,17 +10,16 @@ import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; public record ServerDataPacket(@Nullable Component motd, @Nullable String iconBase64, - boolean previewsChat, boolean enforcesSecureChat) implements ServerPacket { + boolean enforcesSecureChat) implements ServerPacket { public ServerDataPacket(@NotNull NetworkBuffer reader) { this(reader.readOptional(COMPONENT), reader.readOptional(STRING), - reader.read(BOOLEAN), reader.read(BOOLEAN)); + reader.read(BOOLEAN)); } @Override public void write(@NotNull NetworkBuffer writer) { writer.writeOptional(COMPONENT, this.motd); writer.writeOptional(STRING, this.iconBase64); - writer.write(BOOLEAN, previewsChat); writer.write(BOOLEAN, enforcesSecureChat); } diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java deleted file mode 100644 index 3bcae23e9..000000000 --- a/src/main/java/net/minestom/server/network/packet/server/play/SetChatPreviewPacket.java +++ /dev/null @@ -1,24 +0,0 @@ -package net.minestom.server.network.packet.server.play; - -import net.minestom.server.network.NetworkBuffer; -import net.minestom.server.network.packet.server.ServerPacket; -import net.minestom.server.network.packet.server.ServerPacketIdentifier; -import org.jetbrains.annotations.NotNull; - -import static net.minestom.server.network.NetworkBuffer.BOOLEAN; - -public record SetChatPreviewPacket(boolean enable) implements ServerPacket { - public SetChatPreviewPacket(@NotNull NetworkBuffer reader) { - this(reader.read(BOOLEAN)); - } - - @Override - public void write(@NotNull NetworkBuffer writer) { - writer.write(BOOLEAN, enable); - } - - @Override - public int getId() { - return ServerPacketIdentifier.SET_DISPLAY_CHAT_PREVIEW; - } -} diff --git a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java index efa75ce7a..a52f5a99c 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/SoundEffectPacket.java @@ -8,27 +8,78 @@ import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.sound.SoundEvent; import org.jetbrains.annotations.NotNull; +import org.jetbrains.annotations.Nullable; import static net.minestom.server.network.NetworkBuffer.*; -public record SoundEffectPacket(int soundId, @NotNull Source source, - int x, int y, int z, - float volume, float pitch, long seed) implements ServerPacket { - public SoundEffectPacket(@NotNull NetworkBuffer reader) { - this(reader.read(VAR_INT), reader.readEnum(Source.class), - reader.read(INT) * 8, reader.read(INT) * 8, reader.read(INT) * 8, - reader.read(FLOAT), reader.read(FLOAT), reader.read(LONG)); +public record SoundEffectPacket( + // only one of soundEvent and soundName may be present + @Nullable SoundEvent soundEvent, + @Nullable String soundName, + @Nullable Float range, // Only allowed with soundName + @NotNull Source source, + int x, + int y, + int z, + float volume, + float pitch, + long seed +) implements ServerPacket { + private static @NotNull SoundEffectPacket fromReader(@NotNull NetworkBuffer reader) { + int soundId = reader.read(VAR_INT); + SoundEvent soundEvent; + String soundName; + Float range = null; + if (soundId == 0) { + soundEvent = null; + soundName = reader.read(STRING); + range = reader.readOptional(FLOAT); + } else { + soundEvent = SoundEvent.fromId(soundId - 1); + soundName = null; + } + return new SoundEffectPacket( + soundEvent, + soundName, + range, + reader.readEnum(Source.class), + reader.read(INT) * 8, + reader.read(INT) * 8, + reader.read(INT) * 8, + reader.read(FLOAT), + reader.read(FLOAT), + reader.read(LONG) + ); } - public SoundEffectPacket(@NotNull SoundEvent sound, @NotNull Source source, - @NotNull Point position, float volume, float pitch) { - this(sound.id(), source, (int) position.x(), (int) position.y(), (int) position.z(), - volume, pitch, 0); + public SoundEffectPacket(@NotNull SoundEvent soundEvent, @Nullable Float range, @NotNull Source source, + @NotNull Point position, float volume, float pitch, long seed) { + this(soundEvent, null, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); + } + + public SoundEffectPacket(@NotNull String soundName, @Nullable Float range, @NotNull Source source, + @NotNull Point position, float volume, float pitch, long seed) { + this(null, soundName, range, source, position.blockX(), position.blockY(), position.blockZ(), volume, pitch, seed); + } + + public SoundEffectPacket(@NotNull NetworkBuffer reader) { + this(fromReader(reader)); + } + + private SoundEffectPacket(@NotNull SoundEffectPacket packet) { + this(packet.soundEvent, packet.soundName, packet.range, packet.source, + packet.x, packet.y, packet.z, packet.volume, packet.pitch, packet.seed); } @Override public void write(@NotNull NetworkBuffer writer) { - writer.write(VAR_INT, soundId); + if (soundEvent != null) { + writer.write(VAR_INT, soundEvent.id() + 1); + } else { + writer.write(VAR_INT, 0); + writer.write(STRING, soundName); + writer.writeOptional(FLOAT, range); + } writer.write(VAR_INT, AdventurePacketConvertor.getSoundSourceValue(source)); writer.write(INT, x * 8); writer.write(INT, y * 8); @@ -42,4 +93,5 @@ public record SoundEffectPacket(int soundId, @NotNull Source source, public int getId() { return ServerPacketIdentifier.SOUND_EFFECT; } + } diff --git a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java index ead02aaa7..aa9788a68 100644 --- a/src/test/java/net/minestom/server/network/PacketWriteReadTest.java +++ b/src/test/java/net/minestom/server/network/PacketWriteReadTest.java @@ -4,9 +4,12 @@ import com.google.gson.JsonObject; import net.kyori.adventure.bossbar.BossBar; import net.kyori.adventure.text.Component; import net.minestom.server.coordinate.Vec; +import net.minestom.server.crypto.ChatSession; +import net.minestom.server.crypto.PlayerPublicKey; import net.minestom.server.entity.EquipmentSlot; import net.minestom.server.entity.GameMode; import net.minestom.server.entity.Metadata; +import net.minestom.server.entity.PlayerSkin; import net.minestom.server.item.ItemStack; import net.minestom.server.item.Material; import net.minestom.server.network.packet.client.ClientPacket; @@ -19,11 +22,18 @@ import net.minestom.server.network.packet.server.login.SetCompressionPacket; import net.minestom.server.network.packet.server.play.*; import net.minestom.server.network.packet.server.play.DeclareRecipesPacket.Ingredient; import net.minestom.server.network.packet.server.status.PongPacket; +import net.minestom.server.utils.crypto.KeyUtils; +import org.apache.commons.net.util.Base64; import org.jglrxavpok.hephaistos.nbt.NBT; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import java.lang.reflect.InvocationTargetException; +import java.nio.charset.StandardCharsets; +import java.security.KeyFactory; +import java.security.PublicKey; +import java.security.spec.X509EncodedKeySpec; +import java.time.Instant; import java.util.ArrayList; import java.util.List; import java.util.Map; @@ -110,18 +120,20 @@ public class PacketWriteReadTest { SERVER_PACKETS.add(new EntityPropertiesPacket(5, List.of())); SERVER_PACKETS.add(new EntityRotationPacket(5, 45f, 45f, false)); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - new PlayerInfoPacket.UpdateDisplayName(UUID.randomUUID(), COMPONENT))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_DISPLAY_NAME, - new PlayerInfoPacket.UpdateDisplayName(UUID.randomUUID(), (Component) null))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_GAMEMODE, - new PlayerInfoPacket.UpdateGameMode(UUID.randomUUID(), GameMode.CREATIVE))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.UPDATE_LATENCY, - new PlayerInfoPacket.UpdateLatency(UUID.randomUUID(), 5))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.ADD_PLAYER, - new PlayerInfoPacket.AddPlayer(UUID.randomUUID(), "TheMode911", - List.of(new PlayerInfoPacket.AddPlayer.Property("name", "value")), GameMode.CREATIVE, 5, COMPONENT, null))); - SERVER_PACKETS.add(new PlayerInfoPacket(PlayerInfoPacket.Action.REMOVE_PLAYER, new PlayerInfoPacket.RemovePlayer(UUID.randomUUID()))); + final PlayerSkin skin = new PlayerSkin("hh", "hh"); + List prop = List.of(new PlayerInfoUpdatePacket.Property("textures", skin.textures(), skin.signature())); + + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.ADD_PLAYER, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "TheMode911", prop, false, 0, GameMode.SURVIVAL, null, null))); +// SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_DISPLAY_NAME, +// new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), false, 0, GameMode.SURVIVAL, Component.text("NotTheMode911"), null))); + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_GAME_MODE, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), false, 0, GameMode.CREATIVE, null, null))); + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LATENCY, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), false, 20, GameMode.SURVIVAL, null, null))); + SERVER_PACKETS.add(new PlayerInfoUpdatePacket(PlayerInfoUpdatePacket.Action.UPDATE_LISTED, + new PlayerInfoUpdatePacket.Entry(UUID.randomUUID(), "", List.of(), true, 0, GameMode.SURVIVAL, null, null))); + SERVER_PACKETS.add(new PlayerInfoRemovePacket(UUID.randomUUID())); //SERVER_PACKETS.add(new MultiBlockChangePacket(5,5,5,true, new long[]{0,5,543534,1321})); }