From 076f4d558b5c3c6af8b8f172492c38654bdf7c86 Mon Sep 17 00:00:00 2001 From: TheMode Date: Sat, 10 Aug 2019 04:16:01 +0200 Subject: [PATCH] Added chunks & some location packets --- build.gradle | 1 + .../fr/themode/minestom/entity/Player.java | 20 +++++ .../handler/ClientPlayPacketsHandler.java | 6 +- .../packet/client/login/LoginStartPacket.java | 35 +++----- .../client/play/ClientPlayerLookPacket.java | 24 ++++++ .../ClientPlayerPositionAndLookPacket.java | 4 +- .../play/ClientPlayerPositionPacket.java | 25 ++++++ .../packet/server/play/ChunkDataPacket.java | 11 ++- .../java/fr/themode/minestom/utils/Utils.java | 45 ++++++++++ .../minestom/world/ChunkPacketCreator.java | 82 +++++++++++++++++++ .../themode/minestom/world/CustomBiome.java | 24 ++++++ .../themode/minestom/world/CustomBlock.java | 37 +++++++++ .../themode/minestom/world/CustomChunk.java | 41 ++++++++++ .../themode/minestom/world/CustomWorld.java | 51 ++++++++++++ 14 files changed, 377 insertions(+), 29 deletions(-) create mode 100644 src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerLookPacket.java create mode 100644 src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionPacket.java create mode 100644 src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java create mode 100644 src/main/java/fr/themode/minestom/world/CustomBiome.java create mode 100644 src/main/java/fr/themode/minestom/world/CustomBlock.java create mode 100644 src/main/java/fr/themode/minestom/world/CustomChunk.java create mode 100644 src/main/java/fr/themode/minestom/world/CustomWorld.java diff --git a/build.gradle b/build.gradle index 39c249ed7..70ae49c57 100644 --- a/build.gradle +++ b/build.gradle @@ -15,4 +15,5 @@ repositories { dependencies { testCompile group: 'junit', name: 'junit', version: '4.12' implementation 'com.github.Adamaq01:ozao-net:2.3.1' + compile 'com.github.Querz:NBT:4.1' } diff --git a/src/main/java/fr/themode/minestom/entity/Player.java b/src/main/java/fr/themode/minestom/entity/Player.java index e490d1b21..0b40f255e 100644 --- a/src/main/java/fr/themode/minestom/entity/Player.java +++ b/src/main/java/fr/themode/minestom/entity/Player.java @@ -4,9 +4,29 @@ import fr.themode.minestom.net.player.PlayerConnection; public class Player { + private double x, y, z; + private float yaw, pitch; + private boolean onGround; + private PlayerConnection playerConnection; public PlayerConnection getPlayerConnection() { return playerConnection; } + + public void refreshPosition(double x, double y, double z) { + this.x = x; + this.y = y; + this.z = z; + } + + public void refreshView(float yaw, float pitch) { + this.yaw = yaw; + this.pitch = pitch; + } + + public void refreshOnGround(boolean onGround) { + this.onGround = onGround; + } + } diff --git a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java index 8060245a6..36b085228 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/handler/ClientPlayPacketsHandler.java @@ -1,15 +1,13 @@ package fr.themode.minestom.net.packet.client.handler; -import fr.themode.minestom.net.packet.client.play.ClientPlayerPositionAndLookPacket; -import fr.themode.minestom.net.packet.client.play.ClientPluginMessagePacket; -import fr.themode.minestom.net.packet.client.play.ClientSettingsPacket; -import fr.themode.minestom.net.packet.client.play.ClientTeleportConfirmPacket; +import fr.themode.minestom.net.packet.client.play.*; public class ClientPlayPacketsHandler extends ClientPacketsHandler { public ClientPlayPacketsHandler() { register(0x05, ClientSettingsPacket.class); register(0x0B, ClientPluginMessagePacket.class); + register(0x11, ClientPlayerPositionPacket.class); register(0x12, ClientPlayerPositionAndLookPacket.class); register(0x00, ClientTeleportConfirmPacket.class); } diff --git a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java index 91ff21503..0ac3d3783 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/login/LoginStartPacket.java @@ -1,18 +1,18 @@ package fr.themode.minestom.net.packet.client.login; import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; import fr.themode.minestom.entity.GameMode; import fr.themode.minestom.net.ConnectionManager; import fr.themode.minestom.net.ConnectionState; import fr.themode.minestom.net.packet.client.ClientPreplayPacket; import fr.themode.minestom.net.packet.server.login.JoinGamePacket; import fr.themode.minestom.net.packet.server.login.LoginSuccessPacket; -import fr.themode.minestom.net.packet.server.play.ChunkDataPacket; import fr.themode.minestom.net.packet.server.play.PlayerPositionAndLookPacket; import fr.themode.minestom.net.packet.server.play.SpawnPositionPacket; import fr.themode.minestom.net.player.PlayerConnection; import fr.themode.minestom.utils.Utils; -import fr.themode.minestom.world.Dimension; +import fr.themode.minestom.world.*; public class LoginStartPacket implements ClientPreplayPacket { @@ -44,31 +44,22 @@ public class LoginStartPacket implements ClientPreplayPacket { // TODO player abilities - for (int x = 0; x < 8; x++) { - for (int z = 0; z < 8; z++) { - ChunkDataPacket.ChunkSection chunkSection = new ChunkDataPacket.ChunkSection(); - chunkSection.bitsPerBlock = 13; - chunkSection.data = new long[]{0x1001880C0060020L, 0x200D0068004C020L, 1111L}; + CustomChunk customChunk = new CustomChunk(CustomBiome.VOID); + for (int x = 0; x < 16; x++) + for (int z = 0; z < 16; z++) + customChunk.setBlock(x, 4, z, new CustomBlock(1)); // Stone - ChunkDataPacket chunkDataPacket = new ChunkDataPacket(); - chunkDataPacket.columnX = x; - chunkDataPacket.columnZ = z; - chunkDataPacket.fullChunk = true; - chunkDataPacket.mask = 0xFFFF; // 16 bits - ChunkDataPacket.ChunkSection[] sections = new ChunkDataPacket.ChunkSection[16]; - for (int i = 0; i < 16; i++) { - sections[i] = chunkSection; - } - chunkDataPacket.chunkSections = sections; - - connection.sendPacket(chunkDataPacket); + for (int x = -5; x < 5; x++) { + for (int z = -5; z < 5; z++) { + Packet packet = ChunkPacketCreator.create(x, z, customChunk, 0, 15); + connection.getConnection().sendPacket(packet); } } SpawnPositionPacket spawnPositionPacket = new SpawnPositionPacket(); - spawnPositionPacket.x = 50; - spawnPositionPacket.y = 5; - spawnPositionPacket.z = 50; + spawnPositionPacket.x = 0; + spawnPositionPacket.y = 18; + spawnPositionPacket.z = 0; connection.sendPacket(spawnPositionPacket); PlayerPositionAndLookPacket playerPositionAndLookPacket = new PlayerPositionAndLookPacket(); diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerLookPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerLookPacket.java new file mode 100644 index 000000000..74c55459d --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerLookPacket.java @@ -0,0 +1,24 @@ +package fr.themode.minestom.net.packet.client.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; + +public class ClientPlayerLookPacket implements ClientPlayPacket { + + public float yaw, pitch; + public boolean onGround; + + @Override + public void process(Player player) { + player.refreshView(yaw, pitch); + player.refreshOnGround(onGround); + } + + @Override + public void read(Buffer buffer) { + this.yaw = buffer.getFloat(); + this.pitch = buffer.getFloat(); + this.onGround = buffer.getBoolean(); + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionAndLookPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionAndLookPacket.java index ce45126b7..665e75cbd 100644 --- a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionAndLookPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionAndLookPacket.java @@ -12,7 +12,9 @@ public class ClientPlayerPositionAndLookPacket implements ClientPlayPacket { @Override public void process(Player player) { - + player.refreshPosition(x, y, z); + player.refreshView(yaw, pitch); + player.refreshOnGround(onGround); } @Override diff --git a/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionPacket.java b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionPacket.java new file mode 100644 index 000000000..563e8cf67 --- /dev/null +++ b/src/main/java/fr/themode/minestom/net/packet/client/play/ClientPlayerPositionPacket.java @@ -0,0 +1,25 @@ +package fr.themode.minestom.net.packet.client.play; + +import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.entity.Player; +import fr.themode.minestom.net.packet.client.ClientPlayPacket; + +public class ClientPlayerPositionPacket implements ClientPlayPacket { + + public double x, y, z; + public boolean onGround; + + @Override + public void process(Player player) { + player.refreshPosition(x, y, z); + player.refreshOnGround(onGround); + } + + @Override + public void read(Buffer buffer) { + this.x = buffer.getDouble(); + this.y = buffer.getDouble(); + this.z = buffer.getDouble(); + this.onGround = buffer.getBoolean(); + } +} diff --git a/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java b/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java index a492a2a31..8cfed70ee 100644 --- a/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java +++ b/src/main/java/fr/themode/minestom/net/packet/server/play/ChunkDataPacket.java @@ -25,16 +25,23 @@ public class ChunkDataPacket implements ServerPacket { // Nbt buffer.putByte((byte) 10); + buffer.putShort((short) 0); + buffer.putByte((byte) 12); buffer.putShort((short) "MOTION_BLOCKING".length()); try { buffer.putBytes("MOTION_BLOCKING".getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } + buffer.putInt(256); + for (int i = 0; i < 256; i++) { + buffer.putLong(Long.MAX_VALUE); + } + buffer.putByte((byte) 12); - buffer.putShort((short) "MOTION_BLOCKING".length()); + buffer.putShort((short) "WORLD_SURFACE".length()); try { - buffer.putBytes("MOTION_BLOCKING".getBytes("UTF-8")); + buffer.putBytes("WORLD_SURFACE".getBytes("UTF-8")); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } diff --git a/src/main/java/fr/themode/minestom/utils/Utils.java b/src/main/java/fr/themode/minestom/utils/Utils.java index c74bbafa1..aa29aaff7 100644 --- a/src/main/java/fr/themode/minestom/utils/Utils.java +++ b/src/main/java/fr/themode/minestom/utils/Utils.java @@ -1,8 +1,11 @@ package fr.themode.minestom.utils; import fr.adamaq01.ozao.net.Buffer; +import fr.themode.minestom.world.CustomBlock; import java.io.UnsupportedEncodingException; +import java.util.Arrays; +import java.util.stream.Collectors; public class Utils { @@ -121,4 +124,46 @@ public class Utils { buffer.putLong(((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF)); } + public static void writeBlocks(Buffer buffer, CustomBlock[] blocks, int bitsPerEntry) { + buffer.putShort((short) Arrays.stream(blocks).filter(customBlock -> customBlock.getType() != 0).collect(Collectors.toList()).size()); + buffer.putByte((byte) bitsPerEntry); + int[] blocksData = new int[16 * 16 * 16]; + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int sectionIndex = (((y * 16) + x) * 16) + z; + int index = y << 8 | z << 4 | x; + blocksData[index] = blocks[sectionIndex].getType(); + } + } + } + long[] data = encodeBlocks(blocksData, 14); + writeVarInt(buffer, data.length); + for (int i = 0; i < data.length; i++) { + buffer.putLong(data[i]); + } + } + + public static long[] encodeBlocks(int[] blocks, int bitsPerEntry) { + long maxEntryValue = (1L << bitsPerEntry) - 1; + + int length = (int) Math.ceil(blocks.length * bitsPerEntry / 64.0); + long[] data = new long[length]; + + for (int index = 0; index < blocks.length; index++) { + int value = blocks[index]; + int bitIndex = index * bitsPerEntry; + int startIndex = bitIndex / 64; + int endIndex = ((index + 1) * bitsPerEntry - 1) / 64; + int startBitSubIndex = bitIndex % 64; + data[startIndex] = data[startIndex] & ~(maxEntryValue << startBitSubIndex) | ((long) value & maxEntryValue) << startBitSubIndex; + if (startIndex != endIndex) { + int endBitSubIndex = 64 - startBitSubIndex; + data[endIndex] = data[endIndex] >>> endBitSubIndex << endBitSubIndex | ((long) value & maxEntryValue) >> endBitSubIndex; + } + } + + return data; + } + } diff --git a/src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java b/src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java new file mode 100644 index 000000000..d5fc04087 --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/ChunkPacketCreator.java @@ -0,0 +1,82 @@ +package fr.themode.minestom.world; + +import fr.adamaq01.ozao.net.Buffer; +import fr.adamaq01.ozao.net.packet.Packet; +import fr.themode.minestom.utils.Utils; +import net.querz.nbt.CompoundTag; +import net.querz.nbt.LongArrayTag; + +import java.io.ByteArrayOutputStream; +import java.io.DataOutputStream; +import java.io.IOException; + +public class ChunkPacketCreator { + + public static Packet create(int chunkX, int chunkZ, CustomChunk customChunk, int start, int end) { + Packet packet = Packet.create(); + packet.put("id", 0x21); + Buffer payload = packet.getPayload(); + + payload.putInt(chunkX); + payload.putInt(chunkZ); + payload.putBoolean(true); // Send biome data (loading chunk, not modifying it) + int mask = 0; + Buffer blocks = Buffer.create(); + for (int i = 0; i < 16; i++) { + mask |= 1 << i; + CustomBlock[] section = getSection(customChunk, i); + Utils.writeBlocks(blocks, section, 14); + } + // Biome data + int[] biomeData = new int[256]; + for (int z = 0; z < 16; z++) { + for (int x = 0; x < 16; x++) { + biomeData[z * 16 | x] = customChunk.getBiome().getId(); + } + } + for (int i = 0; i < biomeData.length; i++) { + blocks.putInt(biomeData[i]); + } + Utils.writeVarInt(payload, mask); + + // Heightmap + int[] motionBlocking = new int[16 * 16]; + int[] worldSurface = new int[16 * 16]; + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + motionBlocking[x + z * 16] = 4; + worldSurface[x + z * 16] = 5; + } + } + CompoundTag compound = new CompoundTag(); + compound.put("MOTION_BLOCKING", new LongArrayTag(Utils.encodeBlocks(motionBlocking, 9))); + compound.put("WORLD_SURFACE", new LongArrayTag(Utils.encodeBlocks(worldSurface, 9))); + ByteArrayOutputStream outputStream = new ByteArrayOutputStream(); + try { + compound.serialize(new DataOutputStream(outputStream), 100); + } catch (IOException e) { + e.printStackTrace(); + } + byte[] data = outputStream.toByteArray(); + payload.putBytes(data); + + Utils.writeVarInt(payload, blocks.length()); + payload.putBuffer(blocks); + Utils.writeVarInt(payload, 0); + + return packet; + } + + public static CustomBlock[] getSection(CustomChunk customChunk, int section) { + CustomBlock[] blocks = new CustomBlock[16 * 16 * 16]; + for (int y = 0; y < 16; y++) { + for (int x = 0; x < 16; x++) { + for (int z = 0; z < 16; z++) { + int index = (((y * 16) + x) * 16) + z; + blocks[index] = customChunk.getBlock(x, y + 16 * section, z); + } + } + } + return blocks; + } +} diff --git a/src/main/java/fr/themode/minestom/world/CustomBiome.java b/src/main/java/fr/themode/minestom/world/CustomBiome.java new file mode 100644 index 000000000..516dc0f3b --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/CustomBiome.java @@ -0,0 +1,24 @@ +package fr.themode.minestom.world; + +import java.util.Arrays; + +public enum CustomBiome { + + OCEAN(0), + PLAINS(1), + VOID(127); + + private int id; + + CustomBiome(int id) { + this.id = id; + } + + public static CustomBiome fromId(int id) { + return Arrays.stream(values()).filter(customBiome -> customBiome.id == id).findFirst().get(); + } + + public int getId() { + return id; + } +} diff --git a/src/main/java/fr/themode/minestom/world/CustomBlock.java b/src/main/java/fr/themode/minestom/world/CustomBlock.java new file mode 100644 index 000000000..37ac580f4 --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/CustomBlock.java @@ -0,0 +1,37 @@ +package fr.themode.minestom.world; + +public class CustomBlock { + + private short typeAndDamage; + + public CustomBlock(int type) { + this.typeAndDamage = (short) (type & 0x0FFF); + this.typeAndDamage |= (0 << 12) & 0xF000; + } + + public CustomBlock(int type, int damage) { + this.typeAndDamage = (short) (type & 0x0FFF); + this.typeAndDamage |= (damage << 12) & 0xF000; + } + + public int getType() { + return typeAndDamage & 0x0FFF; + } + + public void setType(int type) { + this.typeAndDamage |= type & 0x0FFF; + } + + public int getDamage() { + return (typeAndDamage & 0xF000) >>> 12; + } + + public void setDamage(int damage) { + this.typeAndDamage |= (damage << 12) & 0xF000; + } + + @Override + public String toString() { + return String.format("CustomBlock{type=%s, damage=%s}", getType(), getDamage()); + } +} diff --git a/src/main/java/fr/themode/minestom/world/CustomChunk.java b/src/main/java/fr/themode/minestom/world/CustomChunk.java new file mode 100644 index 000000000..7653c50df --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/CustomChunk.java @@ -0,0 +1,41 @@ +package fr.themode.minestom.world; + +import java.util.HashMap; + +public class CustomChunk { + + private CustomBiome biome; + private HashMap blocks; + + public CustomChunk(CustomBiome biome) { + this.biome = biome; + this.blocks = new HashMap<>(); + } + + public CustomBiome getBiome() { + return biome; + } + + public HashMap getBlocks() { + return blocks; + } + + public void setBlock(int x, int y, int z, CustomBlock customBlock) { + short index = (short) (x & 0x000F); + index |= (y << 4) & 0x0FF0; + index |= (z << 12) & 0xF000; + this.blocks.put(index, customBlock); + } + + public CustomBlock getBlock(int x, int y, int z) { + short index = (short) (x & 0x000F); + index |= (y << 4) & 0x0FF0; + index |= (z << 12) & 0xF000; + return this.blocks.getOrDefault(index, new CustomBlock(0)); + } + + @Override + public String toString() { + return String.format("CustomChunk{biome=%s, blocks=%s}", biome, blocks.values()); + } +} diff --git a/src/main/java/fr/themode/minestom/world/CustomWorld.java b/src/main/java/fr/themode/minestom/world/CustomWorld.java new file mode 100644 index 000000000..ced029b0c --- /dev/null +++ b/src/main/java/fr/themode/minestom/world/CustomWorld.java @@ -0,0 +1,51 @@ +package fr.themode.minestom.world; + +import java.util.HashMap; + +public class CustomWorld { + + private String name; + private HashMap chunks; + + public CustomWorld(String name) { + this.name = name; + this.chunks = new HashMap<>(); + } + + public String getName() { + return name; + } + + public HashMap getChunks() { + return chunks; + } + + public void setChunk(int x, int z, CustomChunk customChunk) { + int index = x & 0x0000FFFF; + index |= (z << 16) & 0xFFFF0000; + this.chunks.put(index, customChunk); + } + + public CustomChunk getChunk(int x, int z) { + int index = x & 0x0000FFFF; + index |= (z << 16) & 0xFFFF0000; + return this.chunks.get(index); + } + + public void setBlock(int x, int y, int z, CustomBlock customBlock) { + int chunkX = x / 16; + int chunkZ = z / 16; + getChunk(chunkX, chunkZ).setBlock(x % 16, y, z % 16, customBlock); + } + + public CustomBlock getBlock(int x, int y, int z) { + int chunkX = x / 16; + int chunkZ = z / 16; + return getChunk(chunkX, chunkZ).getBlock(x % 16, y, z % 16); + } + + @Override + public String toString() { + return String.format("CustomWorld{name=%s, chunks=%s}", name, chunks.values()); + } +}