From 0d72a8a29cb5741a1585da7630e81ff92cc40dfa Mon Sep 17 00:00:00 2001 From: DmitriyMX Date: Sun, 9 May 2021 15:17:44 +0300 Subject: [PATCH] PlayerListItemPacket --- protocol/src/main/java/mc/protocol/State.java | 1 + .../packets/server/PlayerListItemPacket.java | 171 ++++++++++++++++++ .../main/java/mc/server/PacketHandler.java | 24 ++- 3 files changed, 193 insertions(+), 3 deletions(-) create mode 100644 protocol/src/main/java/mc/protocol/packets/server/PlayerListItemPacket.java diff --git a/protocol/src/main/java/mc/protocol/State.java b/protocol/src/main/java/mc/protocol/State.java index 70cfb0f..5bc1735 100644 --- a/protocol/src/main/java/mc/protocol/State.java +++ b/protocol/src/main/java/mc/protocol/State.java @@ -59,6 +59,7 @@ public enum State { ChunkDataPacket.class, 0x20, JoinGamePacket.class, 0x23, PlayerAbilitiesPacket.class,0x2C, + PlayerListItemPacket.class,0x2E, SPlayerPositionAndLookPacket.class, 0x2F, SpawnPositionPacket.class, 0x46 ) diff --git a/protocol/src/main/java/mc/protocol/packets/server/PlayerListItemPacket.java b/protocol/src/main/java/mc/protocol/packets/server/PlayerListItemPacket.java new file mode 100644 index 0000000..7bf2a8f --- /dev/null +++ b/protocol/src/main/java/mc/protocol/packets/server/PlayerListItemPacket.java @@ -0,0 +1,171 @@ +package mc.protocol.packets.server; + +import lombok.*; +import mc.protocol.io.NetByteBuf; +import mc.protocol.packets.ServerSidePacket; +import mc.protocol.utils.GameMode; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.UUID; +import java.util.function.BiConsumer; + +/** + * Player List Item packet. + * + *

Обновление списка игроков, по кнопке TAB.

+ * + *

Структура пакета

+ *
+ * | FIELD             | TYPE        | NOTES                                           |
+ * |-------------------|-------------|-------------------------------------------------|
+ * | Action            | VarInt      | Определяет поля для секции "Player"             |
+ * | Number of players | VarInt      | Количество элементов в секции "Players"         |
+ * | Players           | Array       | Перечисление полей для каждого игрока (player). |
+ * |                   |             | Поля определяются по "Action"                   |
+ * 
+ * + *

Варианты "Action"

+ *
+ * | ACTION                      | PLAYER FIELD         | TYPE        | NOTES                                              |
+ * | VALUE | DESCRIPTION         |                      |             |                                                    |
+ * |-------|---------------------|----------------------|-------------|----------------------------------------------------|
+ * | 0     | add player          | UUID                 | UUID        |                                                    |
+ * |       |                     | Name                 | String (16) |                                                    |
+ * |       |                     | Number Of Properties | VarInt      | Количество элементов "Properties"                  |
+ * |       |                     | Properties           | -           | см. поля ниже                                      |
+ * |       |                     | Gamemode             | VarInt      |                                                    |
+ * |       |                     | Ping                 | VarInt      | в милисекундах                                     |
+ * |       |                     | Has Display Name     | Boolean     |                                                    |
+ * |       |                     | Display Name         | Text        | Опционально. Только если "Has Display Name" = true |
+ * | 1     | update gamemode     | UUID                 | UUID        |                                                    |
+ * |       |                     | Gamemode             | VarInt      |                                                    |
+ * | 2     | update latency      | UUID                 | UUID        |                                                    |
+ * |       |                     | Ping                 | VarInt      | (см. выше)                                         |
+ * | 3     | update display name | UUID                 | UUID        |                                                    |
+ * |       |                     | Has Display Name     | Boolean     |                                                    |
+ * |       |                     | Display Name         | Text        | (см. выше)                                         |
+ * | 4     | remove player       | UUID                 | UUID        |                                                    |
+ * 
+ * + *

Поля секции "Properties"

+ *
+ * | FIELD     | TYPE           | NOTES                                       |
+ * |-----------|-------------   |---------------------------------------------|
+ * | Name      | String (32767) |                                             |
+ * | Value     | String (32767) |                                             |
+ * | Is Signed | Boolean        |                                             |
+ * | Signature | String (32767) | Опционально. Только если "Is Signed" = true |
+ * 
+ * + * @see Player List Item + */ +@Data +public class PlayerListItemPacket implements ServerSidePacket { + + private final List playerItems = new ArrayList<>(); + + private Action action; + + @Override + public void writeSelf(NetByteBuf netByteBuf) { + netByteBuf.writeVarInt(this.action.getCode()); + netByteBuf.writeVarInt(playerItems.size()); + + BiConsumer consumer = null; + switch (this.action) { + //@formatter:off + case ADD_PLAYER: consumer = this::addPlayer; break; + case UPDATE_GAMEMODE: consumer = this::updateGamemode; break; + case UPDATE_LATENCY: consumer = this::updateLatency; break; + case UPDATE_DISPLAY_NAME: consumer = this::updateDisplayName; break; + case REMOVE_PLAYER: consumer = this::removePlayer; break; + //@formatter:on + } + + Objects.requireNonNull(consumer); + + for (PlayerItem playerItem : playerItems) { + consumer.accept(netByteBuf, playerItem); + } + } + + private void addPlayer(NetByteBuf netByteBuf, PlayerItem playerItem) { + netByteBuf.writeUUID(playerItem.getUuid()); + netByteBuf.writeString(playerItem.getName()); + netByteBuf.writeVarInt(playerItem.getProperties().size()); + + for (PlayerProperties property : playerItem.getProperties()) { + netByteBuf.writeString(property.getName()); + netByteBuf.writeString(property.getValue()); + netByteBuf.writeBoolean(property.getIsSigned()); + if (property.getIsSigned()) { + netByteBuf.writeString(property.getSignature()); + } + } + + netByteBuf.writeVarInt(playerItem.getGamemode().getId()); + netByteBuf.writeVarInt(playerItem.getPing()); + netByteBuf.writeBoolean(playerItem.getHasDisplayName()); + if (playerItem.getHasDisplayName()) { + netByteBuf.writeString(playerItem.getDisplayName()); + } + } + + private void updateGamemode(NetByteBuf netByteBuf, PlayerItem playerItem) { + netByteBuf.writeUUID(playerItem.getUuid()); + netByteBuf.writeVarInt(playerItem.getGamemode().getId()); + } + + private void updateLatency(NetByteBuf netByteBuf, PlayerItem playerItem) { + netByteBuf.writeUUID(playerItem.getUuid()); + netByteBuf.writeVarInt(playerItem.getPing()); + } + + private void updateDisplayName(NetByteBuf netByteBuf, PlayerItem playerItem) { + netByteBuf.writeUUID(playerItem.getUuid()); + netByteBuf.writeBoolean(playerItem.getHasDisplayName()); + if (playerItem.getHasDisplayName()) { + netByteBuf.writeString(playerItem.getDisplayName()); + } + } + + private void removePlayer(NetByteBuf netByteBuf, PlayerItem playerItem) { + netByteBuf.writeUUID(playerItem.getUuid()); + } + + @RequiredArgsConstructor + public enum Action { + ADD_PLAYER(0), + UPDATE_GAMEMODE(1), + UPDATE_LATENCY(2), + UPDATE_DISPLAY_NAME(3), + REMOVE_PLAYER(4); + + @Getter + private final int code; + } + + @Builder(builderClassName = "Builder") + @Value + public static class PlayerItem { + List properties = new ArrayList<>(); + + UUID uuid; + String name; + GameMode gamemode; + Integer ping; + Boolean hasDisplayName; + String displayName; + } + + @Builder(builderClassName = "Builder") + @Value + public static class PlayerProperties { + String name; + String value; + Boolean isSigned; + String signature; + } +} diff --git a/server/src/main/java/mc/server/PacketHandler.java b/server/src/main/java/mc/server/PacketHandler.java index fad97df..05cd6ee 100644 --- a/server/src/main/java/mc/server/PacketHandler.java +++ b/server/src/main/java/mc/server/PacketHandler.java @@ -68,16 +68,20 @@ public class PacketHandler { } public void onLoginStart(ConnectionContext context, LoginStartPacket loginStartPacket) { + var playerUuid = UUID.randomUUID(); + var playerName = loginStartPacket.getName(); + var playerGamemode = GameMode.SURVIVAL; + var loginSuccessPacket = new LoginSuccessPacket(); - loginSuccessPacket.setUuid(UUID.randomUUID()); - loginSuccessPacket.setName(loginStartPacket.getName()); + loginSuccessPacket.setUuid(playerUuid); + loginSuccessPacket.setName(playerName); context.sendNow(loginSuccessPacket); context.setState(State.PLAY); var joinGamePacket = new JoinGamePacket(); joinGamePacket.setEntityId(random.nextInt()); - joinGamePacket.setGameMode(GameMode.SURVIVAL); + joinGamePacket.setGameMode(playerGamemode); joinGamePacket.setDimension(0/*Overworld*/); joinGamePacket.setDifficulty(Difficulty.PEACEFUL); joinGamePacket.setLevelType(LevelType.FLAT); @@ -122,6 +126,20 @@ public class PacketHandler { context.send(pingPacket); context.flushSending(); + + // -- Эксперименты -- // + + var playerListItemPacket = new PlayerListItemPacket(); + playerListItemPacket.setAction(PlayerListItemPacket.Action.ADD_PLAYER); + playerListItemPacket.getPlayerItems().add(PlayerListItemPacket.PlayerItem.builder() + .uuid(playerUuid) + .name(playerName) + .gamemode(playerGamemode) + .ping(100) + .hasDisplayName(false) + .build()); + + context.sendNow(playerListItemPacket); } private static String faviconToBase64(Path iconPath) {