diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 94a75ff5..2dea6791 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -20,6 +20,7 @@ namespace MinecraftClient.Protocol.Handlers { private const int MC18Version = 47; private const int MC19Version = 107; + private const int MC191Version = 108; private const int MC110Version = 210; private int compression_treshold = 0; @@ -38,6 +39,8 @@ namespace MinecraftClient.Protocol.Handlers IAesStream s; TcpClient c; + int currentDimension; + public Protocol18Handler(TcpClient Client, int ProtocolVersion, IMinecraftComHandler Handler, ForgeInfo ForgeInfo) { ConsoleIO.SetAutoCompleteEngine(this); @@ -46,12 +49,6 @@ namespace MinecraftClient.Protocol.Handlers this.protocolversion = ProtocolVersion; this.handler = Handler; this.forgeInfo = ForgeInfo; - - if (Settings.TerrainAndMovements && protocolversion > MC18Version) - { - ConsoleIO.WriteLineFormatted("§8Terrain & Movements currently not handled for that MC version."); - Settings.TerrainAndMovements = false; - } } private Protocol18Handler(TcpClient Client) @@ -142,6 +139,7 @@ namespace MinecraftClient.Protocol.Handlers KeepAlive, JoinGame, ChatMessage, + Respawn, PlayerPositionAndLook, ChunkData, MultiBlockChange, @@ -173,6 +171,7 @@ namespace MinecraftClient.Protocol.Handlers case 0x00: return PacketIncomingType.KeepAlive; case 0x01: return PacketIncomingType.JoinGame; case 0x02: return PacketIncomingType.ChatMessage; + case 0x07: return PacketIncomingType.Respawn; case 0x08: return PacketIncomingType.PlayerPositionAndLook; case 0x21: return PacketIncomingType.ChunkData; case 0x22: return PacketIncomingType.MultiBlockChange; @@ -195,6 +194,7 @@ namespace MinecraftClient.Protocol.Handlers case 0x1F: return PacketIncomingType.KeepAlive; case 0x23: return PacketIncomingType.JoinGame; case 0x0F: return PacketIncomingType.ChatMessage; + case 0x33: return PacketIncomingType.Respawn; case 0x2E: return PacketIncomingType.PlayerPositionAndLook; case 0x20: return PacketIncomingType.ChunkData; case 0x10: return PacketIncomingType.MultiBlockChange; @@ -234,7 +234,6 @@ namespace MinecraftClient.Protocol.Handlers } } // Regular in-game packets - switch (getPacketIncomingType(packetID, protocolversion)) { case PacketIncomingType.KeepAlive: @@ -242,6 +241,16 @@ namespace MinecraftClient.Protocol.Handlers break; case PacketIncomingType.JoinGame: handler.OnGameJoined(); + readNextInt(packetData); + readNextByte(packetData); + if (protocolversion >= MC191Version) + this.currentDimension = readNextInt(packetData); + else + this.currentDimension = (sbyte)readNextByte(packetData); + readNextByte(packetData); + readNextByte(packetData); + readNextString(packetData); + readNextBool(packetData); break; case PacketIncomingType.ChatMessage: string message = readNextString(packetData); @@ -256,6 +265,12 @@ namespace MinecraftClient.Protocol.Handlers catch (ArgumentOutOfRangeException) { /* No message type */ } handler.OnTextReceived(ChatParser.ParseText(message)); break; + case PacketIncomingType.Respawn: + this.currentDimension = readNextInt(packetData); + readNextByte(packetData); + readNextByte(packetData); + readNextString(packetData); + break; case PacketIncomingType.PlayerPositionAndLook: if (Settings.TerrainAndMovements) { @@ -273,7 +288,11 @@ namespace MinecraftClient.Protocol.Handlers handler.UpdateLocation(location); if (protocolversion >= MC19Version) - readNextVarInt(packetData); + { + int teleportID = readNextVarInt(packetData); + // Teleport confirm packet + SendPacket(0x00, getVarInt(teleportID)); + } } break; case PacketIncomingType.ChunkData: @@ -446,11 +465,13 @@ namespace MinecraftClient.Protocol.Handlers SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels))); byte fmlProtocolVersion = readNextByte(packetData); - // There's another value afterwards for the dimension, but we don't need it. if (Settings.DebugMessages) ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion); + if (fmlProtocolVersion >= 1) + this.currentDimension = readNextInt(packetData); + // Tell the server we're running the same version. SendForgeHandshakePacket(FMLHandshakeDiscriminator.ClientHello, new byte[] { fmlProtocolVersion }); @@ -581,51 +602,132 @@ namespace MinecraftClient.Protocol.Handlers private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List cache) { - if (protocolversion < MC19Version && chunksContinuous && chunkMask == 0) + if (protocolversion >= MC19Version) { - //Unload the entire chunk column - handler.GetWorld()[chunkX, chunkZ] = null; - } - else - { - //Load chunk data from the server + // 1.9 and above chunk format + // Unloading chunks is handled by a separate packet for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) { if ((chunkMask & (1 << chunkY)) != 0) { + byte bitsPerBlock = readNextByte(cache); + bool usePalette = (bitsPerBlock <= 8); + + int paletteLength = readNextVarInt(cache); + int[] palette = new int[paletteLength]; + for (int i = 0; i < paletteLength; i++) + { + palette[i] = readNextVarInt(cache); + } + + // Bit mask covering bitsPerBlock bits + // EG, if bitsPerBlock = 5, valueMask = 00011111 in binary + uint valueMask = (uint)((1 << bitsPerBlock) - 1); + + ulong[] dataArray = readNextULongArray(cache); + Chunk chunk = new Chunk(); - //Read chunk data, all at once for performance reasons, and build the chunk object - Queue queue = new Queue(readNextUShortsLittleEndian(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ, cache)); for (int blockY = 0; blockY < Chunk.SizeY; blockY++) + { for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) + { for (int blockX = 0; blockX < Chunk.SizeX; blockX++) - chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue()); + { + int blockNumber = (blockY * Chunk.SizeZ + blockZ) * Chunk.SizeX + blockX; + + int startLong = (blockNumber * bitsPerBlock) / 64; + int startOffset = (blockNumber * bitsPerBlock) % 64; + int endLong = ((blockNumber + 1) * bitsPerBlock - 1) / 64; + + // TODO: In the future a single ushort may not store the entire block id; + // the Block code may need to change. + ushort blockId; + if (startLong == endLong) + { + blockId = (ushort)((dataArray[startLong] >> startOffset) & valueMask); + } + else + { + int endOffset = 64 - startOffset; + blockId = (ushort)((dataArray[startLong] >> startOffset | dataArray[endLong] << endOffset) & valueMask); + } + + if (usePalette) + { + // Get the real block ID out of the palette + blockId = (ushort)palette[blockId]; + } + + chunk[blockX, blockY, blockZ] = new Block(blockId); + } + } + } //We have our chunk, save the chunk into the world if (handler.GetWorld()[chunkX, chunkZ] == null) handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn(); handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk; - } - } - //Skip light information - for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) - { - if ((chunkMask & (1 << chunkY)) != 0) - { //Skip block light readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache); //Skip sky light - if (hasSkyLight) + if (this.currentDimension != -1) + // Sky light is not sent in the nether readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache); } } - //Skip biome metadata - if (chunksContinuous) - readData(Chunk.SizeX * Chunk.SizeZ, cache); + // Don't worry about skipping remaining data since there is no useful data afterwards in 1.9 + // (plus, it would require parsing the tile entity lists' NBT) + } + else + { + // Pre 1.9 chunk format + if (chunksContinuous && chunkMask == 0) { + //Unload the entire chunk column + handler.GetWorld()[chunkX, chunkZ] = null; + } else { + //Load chunk data from the server + for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + { + if ((chunkMask & (1 << chunkY)) != 0) + { + Chunk chunk = new Chunk(); + + //Read chunk data, all at once for performance reasons, and build the chunk object + Queue queue = new Queue(readNextUShortsLittleEndian(Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ, cache)); + for (int blockY = 0; blockY < Chunk.SizeY; blockY++) + for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) + for (int blockX = 0; blockX < Chunk.SizeX; blockX++) + chunk[blockX, blockY, blockZ] = new Block(queue.Dequeue()); + + //We have our chunk, save the chunk into the world + if (handler.GetWorld()[chunkX, chunkZ] == null) + handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn(); + handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk; + } + } + + //Skip light information + for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + { + if ((chunkMask & (1 << chunkY)) != 0) + { + //Skip block light + readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache); + + //Skip sky light + if (hasSkyLight) + readData((Chunk.SizeX * Chunk.SizeY * Chunk.SizeZ) / 2, cache); + } + } + + //Skip biome metadata + if (chunksContinuous) + readData(Chunk.SizeX * Chunk.SizeZ, cache); + } } } @@ -755,9 +857,9 @@ namespace MinecraftClient.Protocol.Handlers } /// - /// Read an unsigned short integer from a cache of bytes and remove it from the cache + /// Read an unsigned long integer from a cache of bytes and remove it from the cache /// - /// The unsigned short integer value + /// The unsigned long integer value private static ulong readNextULong(List cache) { @@ -805,6 +907,20 @@ namespace MinecraftClient.Protocol.Handlers return readData(len, cache); } + /// + /// Reads a length-prefixed array of unsigned long integers and removes it from the cache + /// + /// The unsigned long integer values + + private static ulong[] readNextULongArray(List cache) + { + int len = readNextVarInt(cache); + ulong[] result = new ulong[len]; + for (int i = 0; i < len; i++) + result[i] = readNextULong(cache); + return result; + } + /// /// Read a double from a cache of bytes and remove it from the cache ///