diff --git a/protocol/src/main/java/mc/protocol/Difficulty.java b/protocol/src/main/java/mc/protocol/Difficulty.java new file mode 100644 index 0000000..def20c1 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/Difficulty.java @@ -0,0 +1,15 @@ +package mc.protocol; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum Difficulty { + PEACEFUL(0), + EASY(1), + NORMAL(2), + HARD(3); + + private final int id; +} diff --git a/protocol/src/main/java/mc/protocol/GameMode.java b/protocol/src/main/java/mc/protocol/GameMode.java new file mode 100644 index 0000000..b224ea6 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/GameMode.java @@ -0,0 +1,15 @@ +package mc.protocol; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum GameMode { + SURVIVAL(0), + CREATIVE(1), + ADVENTURE(2), + SPECTATOR(3); + + private final int id; +} diff --git a/protocol/src/main/java/mc/protocol/LevelType.java b/protocol/src/main/java/mc/protocol/LevelType.java new file mode 100644 index 0000000..2b99f36 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/LevelType.java @@ -0,0 +1,16 @@ +package mc.protocol; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Getter +public enum LevelType { + DEFAULT_TYPE("default"), + FLAT("flat"), + LARGE_BIOMES("largeBiomes"), + AMPLIFIED("amplified"), + DEFAULT_1_1("default_1_1"); + + private final String type; +} diff --git a/protocol/src/main/java/mc/protocol/State.java b/protocol/src/main/java/mc/protocol/State.java index 98e929e..a4227dd 100644 --- a/protocol/src/main/java/mc/protocol/State.java +++ b/protocol/src/main/java/mc/protocol/State.java @@ -10,6 +10,7 @@ import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.StatusServerRequestPacket; import mc.protocol.packets.server.DisconnectPacket; +import mc.protocol.packets.server.JoinGamePacket; import mc.protocol.packets.server.LoginSuccessPacket; import mc.protocol.packets.server.StatusServerResponse; @@ -44,6 +45,12 @@ public enum State { DisconnectPacket.class, 0x00, LoginSuccessPacket.class, 0x02 ) + ), + PLAY(3, + // server bound + Map.of(), + // client bound + Map.of(JoinGamePacket.class, 0x23) ); @Nullable diff --git a/protocol/src/main/java/mc/protocol/io/NetByteBuf.java b/protocol/src/main/java/mc/protocol/io/NetByteBuf.java index da59f07..1b038e8 100644 --- a/protocol/src/main/java/mc/protocol/io/NetByteBuf.java +++ b/protocol/src/main/java/mc/protocol/io/NetByteBuf.java @@ -49,6 +49,10 @@ public class NetByteBuf extends ByteBuf { @Delegate private final ByteBuf byteBuf; + public void writeUnsignedByte(int value) { + byteBuf.writeByte((byte)(value & 0xFF)); + } + //region String public String readString() { return readString(Short.MAX_VALUE); diff --git a/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java b/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java new file mode 100644 index 0000000..936bc82 --- /dev/null +++ b/protocol/src/main/java/mc/protocol/packets/server/JoinGamePacket.java @@ -0,0 +1,59 @@ +package mc.protocol.packets.server; + +import lombok.Data; +import mc.protocol.Difficulty; +import mc.protocol.GameMode; +import mc.protocol.LevelType; +import mc.protocol.io.NetByteBuf; +import mc.protocol.packets.ServerSidePacket; + +/** + * Join game packet. + * + *

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

+ *
+ * | FIELD              | TYPE          | NOTES                                                                |
+ * |--------------------|---------------|----------------------------------------------------------------------|
+ * | Entity ID          | Integer       | ID сущности (игрока)                                                 |
+ * | Gamemode           | Unsigned Byte | 0: Survival                                                          |
+ * |                    |               | 1: Creative                                                          |
+ * |                    |               | 2: Adventure                                                         |
+ * |                    |               | 3: Spectator                                                         |
+ * |                    |               | Bit 3 (0x8) is the hardcore flag.                                    |
+ * | Dimension          | Integer       | -1: Nether                                                           |
+ * |                    |               | 0: Overworld                                                         |
+ * |                    |               | 1: End                                                               |
+ * | Difficulty         | Unsigned Byte | 0: peaceful                                                          |
+ * |                    |               | 1: easy                                                              |
+ * |                    |               | 2: normal                                                            |
+ * |                    |               | 3: hard                                                              |
+ * | Max Players        | Unsigned Byte | Когда-то использовался клиентом для                                  |
+ * |                    |               | отображения списка игроков. Теперь не используется                   |
+ * | Level Type         | String (16)   | Принимает одно из значений:                                          |
+ * |                    |               | default, flat, largeBiomes, amplified, default_1_1                   |
+ * | Reduced Debug Info | Boolean       | Если true, то Клиент отображает меньше отладочной информации (в F3?) |
+ * 
+ * + * @see Join Game + */ +@Data +public class JoinGamePacket implements ServerSidePacket { + + private int entityId; + private GameMode gameMode; + private int dimension; + private Difficulty difficulty; + private LevelType levelType; + private boolean reducedDebugInfo; + + @Override + public void writeSelf(NetByteBuf netByteBuf) { + netByteBuf.writeInt(entityId); + netByteBuf.writeUnsignedByte(gameMode.getId()); + netByteBuf.writeInt(dimension); + netByteBuf.writeUnsignedByte(difficulty.getId()); + netByteBuf.writeUnsignedByte(0); // Max Players, unused + netByteBuf.writeString(levelType.getType()); + netByteBuf.writeBoolean(reducedDebugInfo); + } +} diff --git a/server/src/main/java/mc/server/PacketHandler.java b/server/src/main/java/mc/server/PacketHandler.java index b016bd0..f3c8a67 100644 --- a/server/src/main/java/mc/server/PacketHandler.java +++ b/server/src/main/java/mc/server/PacketHandler.java @@ -2,13 +2,13 @@ package mc.server; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; -import mc.protocol.ChannelContext; -import mc.protocol.ProtocolConstant; +import mc.protocol.*; import mc.protocol.model.ServerInfo; import mc.protocol.packets.PingPacket; import mc.protocol.packets.client.HandshakePacket; import mc.protocol.packets.client.LoginStartPacket; import mc.protocol.packets.client.StatusServerRequestPacket; +import mc.protocol.packets.server.JoinGamePacket; import mc.protocol.packets.server.LoginSuccessPacket; import mc.protocol.packets.server.StatusServerResponse; import mc.protocol.serializer.TextSerializer; @@ -20,6 +20,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.Base64; import java.util.Collections; +import java.util.Random; import java.util.UUID; @Slf4j @@ -27,6 +28,7 @@ import java.util.UUID; public class PacketHandler { private final Config config; + private final Random random = new Random(System.currentTimeMillis()); public void onHandshake(ChannelContext channel) { channel.setState(channel.getPacket().getNextState()); @@ -62,7 +64,21 @@ public class PacketHandler { loginSuccessPacket.setUuid(UUID.randomUUID()); loginSuccessPacket.setName(loginStartPacket.getName()); + log.info("{}", loginSuccessPacket); channel.getCtx().writeAndFlush(loginSuccessPacket); + channel.setState(State.PLAY); + + JoinGamePacket joinGamePacket = new JoinGamePacket(); + joinGamePacket.setEntityId(random.nextInt()); + joinGamePacket.setGameMode(GameMode.SPECTATOR); + joinGamePacket.setDimension(0/*Overworld*/); + joinGamePacket.setDifficulty(Difficulty.PEACEFUL); + joinGamePacket.setLevelType(LevelType.FLAT); + + log.info("{}", joinGamePacket); + channel.getCtx().write(joinGamePacket); + + channel.getCtx().flush(); } private static String faviconToBase64(Path iconPath) {