From cb00c28b6e0d55d626c5ebeee9710358c5c373d9 Mon Sep 17 00:00:00 2001 From: ORelio Date: Mon, 30 Nov 2015 15:30:49 +0100 Subject: [PATCH] Add world handling (and fall to ground) - World is now properly parsed and stored from chunk data - Block changes are also handled and world updated accordingly - Added ground checking, the player will move down to reach the ground - Performance tweaking in Protocol18, using lists instead of arrays - Fix player look not properly skipped causing invalid location after teleport --- MinecraftClient/Mapping/Block.cs | 99 ++++++ MinecraftClient/Mapping/Chunk.cs | 63 ++++ MinecraftClient/Mapping/ChunkColumn.cs | 48 +++ MinecraftClient/Mapping/Location.cs | 75 ++++ MinecraftClient/Mapping/World.cs | 103 ++++++ MinecraftClient/McTcpClient.cs | 21 +- MinecraftClient/MinecraftClient.csproj | 4 + .../Protocol/Handlers/Protocol18.cs | 336 +++++++++++++----- .../Protocol/IMinecraftComHandler.cs | 1 + MinecraftClient/Settings.cs | 2 +- 10 files changed, 661 insertions(+), 91 deletions(-) create mode 100644 MinecraftClient/Mapping/Block.cs create mode 100644 MinecraftClient/Mapping/Chunk.cs create mode 100644 MinecraftClient/Mapping/ChunkColumn.cs create mode 100644 MinecraftClient/Mapping/World.cs diff --git a/MinecraftClient/Mapping/Block.cs b/MinecraftClient/Mapping/Block.cs new file mode 100644 index 00000000..29a082af --- /dev/null +++ b/MinecraftClient/Mapping/Block.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Mapping +{ + /// + /// Represents a Minecraft Block + /// + public struct Block + { + /// + /// Storage for block ID and metadata + /// + private ushort blockIdAndMeta; + + /// + /// Id of the block + /// + public short BlockId + { + get + { + return (short)(blockIdAndMeta >> 4); + } + set + { + blockIdAndMeta = (ushort)(value << 4 | BlockMeta); + } + } + + /// + /// Metadata of the block + /// + public byte BlockMeta + { + get + { + return (byte)(blockIdAndMeta & 0x0F); + } + set + { + blockIdAndMeta = (ushort)((blockIdAndMeta & ~0x0F) | (value & 0x0F)); + } + } + + /// + /// Check if the block can be passed through or not + /// + public bool Solid + { + get + { + return BlockId != 0; + } + } + + /// + /// Get a block of the specified type and metadata + /// + /// Block type + /// Block metadata + public Block(short type, byte metadata = 0) + { + this.blockIdAndMeta = 0; + this.BlockId = type; + this.BlockMeta = metadata; + } + + /// + /// Get a block of the specified type and metadata + /// + /// + public Block(ushort typeAndMeta) + { + this.blockIdAndMeta = typeAndMeta; + } + + /// + /// Represents an empty block + /// + public static Block Air + { + get + { + return new Block(0); + } + } + + /// + /// String representation of the block + /// + public override string ToString() + { + return BlockId.ToString() + (BlockMeta != 0 ? ":" + BlockMeta.ToString() : ""); + } + } +} diff --git a/MinecraftClient/Mapping/Chunk.cs b/MinecraftClient/Mapping/Chunk.cs new file mode 100644 index 00000000..a64cc329 --- /dev/null +++ b/MinecraftClient/Mapping/Chunk.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Mapping +{ + /// + /// Represent a chunk of terrain in a Minecraft world + /// + public class Chunk + { + public const int SizeX = 16; + public const int SizeY = 16; + public const int SizeZ = 16; + + /// + /// Blocks contained into the chunk + /// + private readonly Block[,,] blocks = new Block[SizeX, SizeY, SizeZ]; + + /// + /// Read, or set the specified block + /// + /// Block X + /// Block Y + /// Block Z + /// chunk at the given location + public Block this[int blockX, int blockY, int blockZ] + { + get + { + if (blockX < 0 || blockX >= SizeX) + throw new ArgumentOutOfRangeException("blockX", "Must be between 0 and " + (SizeX - 1) + " (inclusive)"); + if (blockY < 0 || blockY >= SizeY) + throw new ArgumentOutOfRangeException("blockY", "Must be between 0 and " + (SizeY - 1) + " (inclusive)"); + if (blockZ < 0 || blockZ >= SizeZ) + throw new ArgumentOutOfRangeException("blockZ", "Must be between 0 and " + (SizeZ - 1) + " (inclusive)"); + return blocks[blockX, blockY, blockZ]; + } + set + { + if (blockX < 0 || blockX >= SizeX) + throw new ArgumentOutOfRangeException("blockX", "Must be between 0 and " + (SizeX - 1) + " (inclusive)"); + if (blockY < 0 || blockY >= SizeY) + throw new ArgumentOutOfRangeException("blockY", "Must be between 0 and " + (SizeY - 1) + " (inclusive)"); + if (blockZ < 0 || blockZ >= SizeZ) + throw new ArgumentOutOfRangeException("blockZ", "Must be between 0 and " + (SizeZ - 1) + " (inclusive)"); + blocks[blockX, blockY, blockZ] = value; + } + } + + /// + /// Get block at the specified location + /// + /// Location, a modulo will be applied + /// The block + public Block GetBlock(Location location) + { + return this[((int)location.X) % Chunk.SizeX, ((int)location.Y) % Chunk.SizeY, ((int)location.Z) % Chunk.SizeZ]; + } + } +} diff --git a/MinecraftClient/Mapping/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs new file mode 100644 index 00000000..26cbab39 --- /dev/null +++ b/MinecraftClient/Mapping/ChunkColumn.cs @@ -0,0 +1,48 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Mapping +{ + /// + /// Represent a column of chunks of terrain in a Minecraft world + /// + public class ChunkColumn + { + public const int ColumnSize = 16; + + /// + /// Blocks contained into the chunk + /// + private readonly Chunk[] chunks = new Chunk[ColumnSize]; + + /// + /// Get or set the specified chunk column + /// + /// ChunkColumn X + /// ChunkColumn Y + /// chunk at the given location + public Chunk this[int chunkY] + { + get + { + return chunks[chunkY]; + } + set + { + chunks[chunkY] = value; + } + } + + /// + /// Get chunk at the specified location + /// + /// Location, a modulo will be applied + /// The chunk, or null if not loaded + public Chunk GetChunk(Location location) + { + return this[location.ChunkY]; + } + } +} diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index 22145ad5..a34f7a0e 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -46,6 +46,72 @@ namespace MinecraftClient.Mapping Z = z; } + /// + /// The X index of the corresponding chunk in the world + /// + public int ChunkX + { + get + { + return ((int)X) / Chunk.SizeX; + } + } + + /// + /// The Y index of the corresponding chunk in the world + /// + public int ChunkY + { + get + { + return ((int)Y) / Chunk.SizeY; + } + } + + /// + /// The Z index of the corresponding chunk in the world + /// + public int ChunkZ + { + get + { + return ((int)Z) / Chunk.SizeY; + } + } + + /// + /// The X index of the corresponding block in the corresponding chunk of the world + /// + public int ChunkBlockX + { + get + { + return ((int)X) % Chunk.SizeX; + } + } + + /// + /// The Y index of the corresponding block in the corresponding chunk of the world + /// + public int ChunkBlockY + { + get + { + return ((int)Y) % Chunk.SizeY; + } + } + + /// + /// The Z index of the corresponding block in the corresponding chunk of the world + /// + public int ChunkBlockZ + { + get + { + return ((int)Z) % Chunk.SizeZ; + } + } + /// /// Compare two locations. Locations are equals if the integer part of their coordinates are equals. /// @@ -155,5 +221,14 @@ namespace MinecraftClient.Mapping | (((int)Y) & ~((~0) << 13)) << 13 | (((int)Z) & ~((~0) << 06)) << 00; } + + /// + /// Convert the location into a string representation + /// + /// String representation of the location + public override string ToString() + { + return String.Format("X:{0} Y:{1} Z:{2}", X, Y, Z); + } } } diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs new file mode 100644 index 00000000..0214d100 --- /dev/null +++ b/MinecraftClient/Mapping/World.cs @@ -0,0 +1,103 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace MinecraftClient.Mapping +{ + /// + /// Represents a Minecraft World + /// + public class World + { + /// + /// The chunks contained into the Minecraft world + /// + private Dictionary> chunks = new Dictionary>(); + + /// + /// Read, set or unload the specified chunk column + /// + /// ChunkColumn X + /// ChunkColumn Y + /// chunk at the given location + public ChunkColumn this[int chunkX, int chunkZ] + { + get + { + //Read a chunk + if (chunks.ContainsKey(chunkX)) + if (chunks[chunkX].ContainsKey(chunkZ)) + return chunks[chunkX][chunkZ]; + return null; + } + set + { + if (value != null) + { + //Update a chunk column + if (!chunks.ContainsKey(chunkX)) + chunks[chunkX] = new Dictionary(); + chunks[chunkX][chunkZ] = value; + } + else + { + //Unload a chunk column + if (chunks.ContainsKey(chunkX)) + { + if (chunks[chunkX].ContainsKey(chunkZ)) + { + chunks[chunkX].Remove(chunkZ); + if (chunks[chunkX].Count == 0) + chunks.Remove(chunkX); + } + } + } + } + } + + /// + /// Get chunk column at the specified location + /// + /// Location to retrieve chunk column + /// The chunk column + public ChunkColumn GetChunkColumn(Location location) + { + return this[location.ChunkX, location.ChunkZ]; + } + + /// + /// Get block at the specified location + /// + /// Location to retrieve block from + /// Block at specified location or Air if the location is not loaded + public Block GetBlock(Location location) + { + ChunkColumn column = GetChunkColumn(location); + if (column != null) + { + Chunk chunk = column.GetChunk(location); + if (chunk != null) + return chunk.GetBlock(location); + } + return Block.Air; + } + + /// + /// Set block at the specified location + /// + /// Location to set block to + /// Block to set + public void SetBlock(Location location, Block block) + { + ChunkColumn column = this[location.ChunkX, location.ChunkZ]; + if (column != null) + { + Chunk chunk = column[location.ChunkY]; + if (chunk == null) + column[location.ChunkY] = chunk = new Chunk(); + chunk[location.ChunkBlockX, location.ChunkBlockY, location.ChunkBlockZ] = block; + } + } + } +} diff --git a/MinecraftClient/McTcpClient.cs b/MinecraftClient/McTcpClient.cs index 66e8bc8a..0e74b0e3 100644 --- a/MinecraftClient/McTcpClient.cs +++ b/MinecraftClient/McTcpClient.cs @@ -31,6 +31,8 @@ namespace MinecraftClient public void BotUnLoad(ChatBot b) { bots.RemoveAll(item => object.ReferenceEquals(item, b)); } public void BotClear() { bots.Clear(); } + private object locationLock = new object(); + private World world = new World(); private Location location; private int updateTicks = 0; @@ -46,6 +48,7 @@ namespace MinecraftClient public string GetUserUUID() { return uuid; } public string GetSessionID() { return sessionid; } public Location GetCurrentLocation() { return location; } + public World GetWorld() { return world; } TcpClient client; IMinecraftCom handler; @@ -345,11 +348,14 @@ namespace MinecraftClient public void UpdateLocation(Location location, bool relative) { - if (relative) + lock (locationLock) { - this.location += location; + if (relative) + { + this.location += location; + } + else this.location = location; } - else this.location = location; } /// @@ -448,7 +454,14 @@ namespace MinecraftClient { if (updateTicks >= 10) { - handler.SendLocationUpdate(location, true); //TODO handle onGround once terrain data is available + lock (locationLock) + { + Location belowMe = location + new Location(0, -1, 0); + Block blockBelowMe = world.GetBlock(belowMe); + handler.SendLocationUpdate(location, blockBelowMe.Solid); + if (!blockBelowMe.Solid) + location = belowMe; + } updateTicks = 0; } updateTicks++; diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index b9a92e7e..a7615537 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -114,6 +114,10 @@ + + + + diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 3b087710..06f4fa30 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -86,9 +86,9 @@ namespace MinecraftClient.Protocol.Handlers while (c.Client.Available > 0) { int packetID = 0; - byte[] packetData = new byte[] { }; - readNextPacket(ref packetID, ref packetData); - handlePacket(packetID, packetData); + List packetData = new List(); + readNextPacket(ref packetID, packetData); + handlePacket(packetID, new List(packetData)); } } catch (SocketException) { return false; } @@ -102,21 +102,26 @@ namespace MinecraftClient.Protocol.Handlers /// will contain packet ID /// will contain raw packet Data - private void readNextPacket(ref int packetID, ref byte[] packetData) + private void readNextPacket(ref int packetID, List packetData) { int size = readNextVarIntRAW(); //Packet size - packetData = readDataRAW(size); //Packet contents + packetData.AddRange(readDataRAW(size)); //Packet contents //Handle packet decompression if (protocolversion >= MC18Version && compression_treshold > 0) { - int size_uncompressed = readNextVarInt(ref packetData); - if (size_uncompressed != 0) // != 0 means compressed, let's decompress - packetData = ZlibUtils.Decompress(packetData, size_uncompressed); + int sizeUncompressed = readNextVarInt(packetData); + if (sizeUncompressed != 0) // != 0 means compressed, let's decompress + { + byte[] toDecompress = packetData.ToArray(); + byte[] uncompressed = ZlibUtils.Decompress(toDecompress, sizeUncompressed); + packetData.Clear(); + packetData.AddRange(uncompressed); + } } - packetID = readNextVarInt(ref packetData); //Packet ID + packetID = readNextVarInt(packetData); //Packet ID } /// @@ -126,7 +131,7 @@ namespace MinecraftClient.Protocol.Handlers /// Packet contents /// TRUE if the packet was processed, FALSE if ignored or unknown - private bool handlePacket(int packetID, byte[] packetData) + private bool handlePacket(int packetID, List packetData) { if (login_phase) { @@ -134,7 +139,7 @@ namespace MinecraftClient.Protocol.Handlers { case 0x03: if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); + compression_treshold = readNextVarInt(packetData); break; default: return false; //Ignored packet @@ -151,11 +156,11 @@ namespace MinecraftClient.Protocol.Handlers handler.OnGameJoined(); break; case 0x02: //Chat message - string message = readNextString(ref packetData); + string message = readNextString(packetData); try { //Hide system messages or xp bar messages? - byte messageType = readData(1, ref packetData)[0]; + byte messageType = readNextByte(packetData); if ((messageType == 1 && !Settings.DisplaySystemMessages) || (messageType == 2 && !Settings.DisplayXPBarMessages)) break; @@ -163,14 +168,14 @@ namespace MinecraftClient.Protocol.Handlers catch (IndexOutOfRangeException) { /* No message type */ } handler.OnTextReceived(ChatParser.ParseText(message)); break; - case 0x08: + case 0x08: //Player Position and Look if (Settings.TerrainAndMovements) { - double x = readNextDouble(ref packetData); - double y = readNextDouble(ref packetData); - double z = readNextDouble(ref packetData); - - byte locMask = readNextByte(ref packetData); + double x = readNextDouble(packetData); + double y = readNextDouble(packetData); + double z = readNextDouble(packetData); + readData(8, packetData); //Ignore look + byte locMask = readNextByte(packetData); Location location = handler.GetCurrentLocation(); location.X = (locMask & 1 << 0) != 0 ? location.X + x : x; @@ -180,18 +185,72 @@ namespace MinecraftClient.Protocol.Handlers handler.UpdateLocation(location); } break; + case 0x21: //Chunk Data + if (Settings.TerrainAndMovements) + { + int chunkX = readNextInt(packetData); + int chunkZ = readNextInt(packetData); + bool chunksContinuous = readNextBool(packetData); + ushort chunkMask = readNextUShort(packetData); + int dataSize = readNextVarInt(packetData); + ProcessChunkColumnData(chunkX, chunkZ, chunkMask, false, chunksContinuous, packetData); + } + break; + case 0x22: //Multi Block Change + if (Settings.TerrainAndMovements) + { + int chunkX = readNextInt(packetData); + int chunkZ = readNextInt(packetData); + int recordCount = readNextVarInt(packetData); + for (int i = 0; i < recordCount; i++) + { + byte locationXZ = readNextByte(packetData); + int blockX = locationXZ >> 4; + int blockZ = locationXZ & 0x0F; + int blockY = (ushort)readNextByte(packetData); + Block block = new Block((ushort)readNextVarInt(packetData)); + handler.GetWorld().SetBlock(new Location(blockX + chunkX * Chunk.SizeX, blockY, blockZ + chunkZ * Chunk.SizeZ), block); + } + } + break; + case 0x23: //Block Change + if (Settings.TerrainAndMovements) + handler.GetWorld().SetBlock(Location.FromLongRepresentation(readNextULong(packetData)), new Block((ushort)readNextVarInt(packetData))); + break; + case 0x26: //Map Chunk Bulk + if (Settings.TerrainAndMovements) + { + bool hasSkyLight = readNextBool(packetData); + int chunkCount = readNextVarInt(packetData); + + //Read chunk records + int[] chunkXs = new int[chunkCount]; + int[] chunkZs = new int[chunkCount]; + ushort[] chunkMasks = new ushort[chunkCount]; + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + { + chunkXs[chunkColumnNo] = readNextInt(packetData); + chunkZs[chunkColumnNo] = readNextInt(packetData); + chunkMasks[chunkColumnNo] = readNextUShort(packetData); + } + + //Process chunk records + for (int chunkColumnNo = 0; chunkColumnNo < chunkCount; chunkColumnNo++) + ProcessChunkColumnData(chunkXs[chunkColumnNo], chunkZs[chunkColumnNo], chunkMasks[chunkColumnNo], hasSkyLight, true, packetData); + } + break; case 0x38: //Player List update if (protocolversion >= MC18Version) { - int action = readNextVarInt(ref packetData); - int numActions = readNextVarInt(ref packetData); + int action = readNextVarInt(packetData); + int numActions = readNextVarInt(packetData); for (int i = 0; i < numActions; i++) { - Guid uuid = readNextUUID(ref packetData); + Guid uuid = readNextUUID(packetData); switch (action) { case 0x00: //Player Join - string name = readNextString(ref packetData); + string name = readNextString(packetData); handler.OnPlayerJoin(uuid, name); break; case 0x04: //Player Leave @@ -205,9 +264,9 @@ namespace MinecraftClient.Protocol.Handlers } else //MC 1.7.X does not provide UUID in tab-list updates { - string name = readNextString(ref packetData); - bool online = readNextBool(ref packetData); - short ping = readNextShort(ref packetData); + string name = readNextString(packetData); + bool online = readNextBool(packetData); + short ping = readNextShort(packetData); Guid FakeUUID = new Guid(MD5.Create().ComputeHash(Encoding.UTF8.GetBytes(name)).Take(16).ToArray()); if (online) handler.OnPlayerJoin(FakeUUID, name); @@ -215,11 +274,11 @@ namespace MinecraftClient.Protocol.Handlers } break; case 0x3A: //Tab-Complete Result - int autocomplete_count = readNextVarInt(ref packetData); + int autocomplete_count = readNextVarInt(packetData); string tab_list = ""; for (int i = 0; i < autocomplete_count; i++) { - autocomplete_result = readNextString(ref packetData); + autocomplete_result = readNextString(packetData); if (autocomplete_result != "") tab_list = tab_list + autocomplete_result + " "; } @@ -229,26 +288,26 @@ namespace MinecraftClient.Protocol.Handlers ConsoleIO.WriteLineFormatted("§8" + tab_list, false); break; case 0x3F: //Plugin message. - String channel = readNextString(ref packetData); + String channel = readNextString(packetData); if (protocolversion < MC18Version) { if (forgeInfo == null) { // 1.7 and lower prefix plugin channel packets with the length. // We can skip it, though. - readNextShort(ref packetData); + readNextShort(packetData); } else { // Forge does something even weirder with the length. - readNextVarShort(ref packetData); + readNextVarShort(packetData); } } if (forgeInfo != null) { if (channel == "FML|HS") { - FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(ref packetData); + FMLHandshakeDiscriminator discriminator = (FMLHandshakeDiscriminator)readNextByte(packetData); if (discriminator == FMLHandshakeDiscriminator.HandshakeReset) { @@ -269,7 +328,7 @@ namespace MinecraftClient.Protocol.Handlers string[] channels = { "FML|HS", "FML", "FML|MP", "FML", "FORGE" }; SendPluginChannelPacket("REGISTER", Encoding.UTF8.GetBytes(string.Join("\0", channels))); - byte fmlProtocolVersion = readNextByte(ref packetData); + byte fmlProtocolVersion = readNextByte(packetData); // There's another value afterwards for the dimension, but we don't need it. ConsoleIO.WriteLineFormatted("§8Forge protocol version : " + fmlProtocolVersion); @@ -316,7 +375,7 @@ namespace MinecraftClient.Protocol.Handlers { // 1.7.10 and below have one registry // with blocks and items. - int registrySize = readNextVarInt(ref packetData); + int registrySize = readNextVarInt(packetData); ConsoleIO.WriteLineFormatted("§8Received registry " + "with " + registrySize + " entries"); @@ -327,9 +386,9 @@ namespace MinecraftClient.Protocol.Handlers { // 1.8+ has more than one registry. - bool hasNextRegistry = readNextBool(ref packetData); - string registryName = readNextString(ref packetData); - int registrySize = readNextVarInt(ref packetData); + bool hasNextRegistry = readNextBool(packetData); + string registryName = readNextString(packetData); + int registrySize = readNextVarInt(packetData); ConsoleIO.WriteLineFormatted("§8Received registry " + registryName + " with " + registrySize + " entries"); @@ -370,15 +429,15 @@ namespace MinecraftClient.Protocol.Handlers } return false; case 0x40: //Kick Packet - handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(ref packetData))); + handler.OnConnectionLost(ChatBot.DisconnectReason.InGameKick, ChatParser.ParseText(readNextString(packetData))); return false; case 0x46: //Network Compression Treshold Info if (protocolversion >= MC18Version) - compression_treshold = readNextVarInt(ref packetData); + compression_treshold = readNextVarInt(packetData); break; case 0x48: //Resource Pack Send - string url = readNextString(ref packetData); - string hash = readNextString(ref packetData); + string url = readNextString(packetData); + string hash = readNextString(packetData); //Send back "accepted" and "successfully loaded" responses for plugins making use of resource pack mandatory SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(3))); SendPacket(0x19, concatBytes(getVarInt(hash.Length), Encoding.UTF8.GetBytes(hash), getVarInt(0))); @@ -389,6 +448,66 @@ namespace MinecraftClient.Protocol.Handlers return true; //Packet processed } + /// + /// Process chunk column data from the server and (un)load the chunk from the Minecraft world + /// + /// Chunk X location + /// Chunk Z location + /// Chunk mask for reading data + /// Contains skylight info + /// Are the chunk continuous + /// Cache for reading chunk data + + private void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, bool hasSkyLight, bool chunksContinuous, List cache) + { + 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); + } + } + /// /// Start the updating thread. Should be called after login success. /// @@ -445,10 +564,10 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The data read from the cache as an array - private static byte[] readData(int offset, ref byte[] cache) + private static byte[] readData(int offset, List cache) { byte[] result = cache.Take(offset).ToArray(); - cache = cache.Skip(offset).ToArray(); + cache.RemoveRange(0, offset); return result; } @@ -458,12 +577,12 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The string - private static string readNextString(ref byte[] cache) + private static string readNextString(List cache) { - int length = readNextVarInt(ref cache); + int length = readNextVarInt(cache); if (length > 0) { - return Encoding.UTF8.GetString(readData(length, ref cache)); + return Encoding.UTF8.GetString(readData(length, cache)); } else return ""; } @@ -473,9 +592,9 @@ namespace MinecraftClient.Protocol.Handlers /// /// The boolean value - private static bool readNextBool(ref byte[] cache) + private static bool readNextBool(List cache) { - return readData(1, ref cache)[0] != 0x00; + return readNextByte(cache) != 0x00; } /// @@ -483,34 +602,79 @@ namespace MinecraftClient.Protocol.Handlers /// /// The short integer value - private static short readNextShort(ref byte[] cache) + private static short readNextShort(List cache) { - byte[] rawValue = readData(2, ref cache); + byte[] rawValue = readData(2, cache); Array.Reverse(rawValue); //Endianness return BitConverter.ToInt16(rawValue, 0); } + /// + /// Read an integer from a cache of bytes and remove it from the cache + /// + /// The integer value + + private static int readNextInt(List cache) + { + byte[] rawValue = readData(4, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToInt32(rawValue, 0); + } + /// /// Read an unsigned short integer from a cache of bytes and remove it from the cache /// /// The unsigned short integer value - private static ushort readNextUShort(ref byte[] cache) + private static ushort readNextUShort(List cache) { - byte[] rawValue = readData(2, ref cache); + byte[] rawValue = readData(2, cache); Array.Reverse(rawValue); //Endianness return BitConverter.ToUInt16(rawValue, 0); } + /// + /// Read an unsigned short integer from a cache of bytes and remove it from the cache + /// + /// The unsigned short integer value + + private static ulong readNextULong(List cache) + { + byte[] rawValue = readData(8, cache); + Array.Reverse(rawValue); //Endianness + return BitConverter.ToUInt64(rawValue, 0); + } + + /// + /// Read several little endian unsigned short integers at once from a cache of bytes and remove them from the cache + /// + /// The unsigned short integer value + + private static ushort[] readNextUShortsLittleEndian(int amount, List cache) + { + byte[] rawValues = readData(2 * amount, cache); + byte[] rawValue = new byte[2]; + ushort[] result = new ushort[amount]; + + for (int i = 0; i < amount; i++) + { + rawValue[0] = rawValues[i * 2]; + rawValue[1] = rawValues[i * 2 + 1]; + result[i] = BitConverter.ToUInt16(rawValue, 0); + } + + return result; + } + /// /// Read a uuid from a cache of bytes and remove it from the cache /// /// Cache of bytes to read from /// The uuid - private static Guid readNextUUID(ref byte[] cache) + private static Guid readNextUUID(List cache) { - return new Guid(readData(16, ref cache)); + return new Guid(readData(16, cache)); } /// @@ -519,12 +683,12 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The byte array - private byte[] readNextByteArray(ref byte[] cache) + private byte[] readNextByteArray(List cache) { int len = protocolversion >= MC18Version - ? readNextVarInt(ref cache) - : readNextShort(ref cache); - return readData(len, ref cache); + ? readNextVarInt(cache) + : readNextShort(cache); + return readData(len, cache); } /// @@ -532,9 +696,9 @@ namespace MinecraftClient.Protocol.Handlers /// /// The double value - private static double readNextDouble(ref byte[] cache) + private static double readNextDouble(List cache) { - byte[] rawValue = readData(8, ref cache); + byte[] rawValue = readData(8, cache); Array.Reverse(rawValue); //Endianness return BitConverter.ToDouble(rawValue, 0); } @@ -567,16 +731,14 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The integer - private static int readNextVarInt(ref byte[] cache) + private static int readNextVarInt(List cache) { int i = 0; int j = 0; int k = 0; - byte[] tmp = new byte[1]; while (true) { - tmp = readData(1, ref cache); - k = tmp[0]; + k = readNextByte(cache); i |= (k & 0x7F) << j++ * 7; if (j > 5) throw new OverflowException("VarInt too big"); if ((k & 0x80) != 128) break; @@ -592,14 +754,14 @@ namespace MinecraftClient.Protocol.Handlers /// Cache of bytes to read from /// The int - private static int readNextVarShort(ref byte[] cache) + private static int readNextVarShort(List cache) { - ushort low = readNextUShort(ref cache); + ushort low = readNextUShort(cache); byte high = 0; if ((low & 0x8000) != 0) { low &= 0x7FFF; - high = readNextByte(ref cache); + high = readNextByte(cache); } return ((high & 0xFF) << 15) | low; } @@ -609,9 +771,11 @@ namespace MinecraftClient.Protocol.Handlers /// /// The byte that was read - private static byte readNextByte(ref byte[] cache) + private static byte readNextByte(List cache) { - return readData(1, ref cache)[0]; + byte result = cache[0]; + cache.RemoveAt(0); + return result; } /// @@ -734,10 +898,10 @@ namespace MinecraftClient.Protocol.Handlers /// packet ID /// packet Data - private void SendPacket(int packetID, byte[] packetData) + private void SendPacket(int packetID, IEnumerable packetData) { //The inner packet - byte[] the_packet = concatBytes(getVarInt(packetID), packetData); + byte[] the_packet = concatBytes(getVarInt(packetID), packetData.ToArray()); if (compression_treshold > 0) //Compression enabled? { @@ -795,20 +959,20 @@ namespace MinecraftClient.Protocol.Handlers SendPacket(0x00, login_packet); int packetID = -1; - byte[] packetData = new byte[] { }; + List packetData = new List(); while (true) { - readNextPacket(ref packetID, ref packetData); + readNextPacket(ref packetID, packetData); if (packetID == 0x00) //Login rejected { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData))); + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData))); return false; } else if (packetID == 0x01) //Encryption request { - string serverID = readNextString(ref packetData); - byte[] Serverkey = readNextByteArray(ref packetData); - byte[] token = readNextByteArray(ref packetData); + string serverID = readNextString(packetData); + byte[] Serverkey = readNextByteArray(packetData); + byte[] token = readNextByteArray(packetData); return StartEncryption(handler.GetUserUUID(), handler.GetSessionID(), token, serverID, Serverkey); } else if (packetID == 0x02) //Login successful @@ -838,15 +1002,15 @@ namespace MinecraftClient.Protocol.Handlers private bool CompleteForgeHandshake() { int packetID = -1; - byte[] packetData = new byte[0]; + List packetData = new List(); while (fmlHandshakeState != FMLHandshakeClientState.DONE) { - readNextPacket(ref packetID, ref packetData); + readNextPacket(ref packetID, packetData); if (packetID == 0x40) // Disconect { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData))); + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData))); return false; } else @@ -893,13 +1057,13 @@ namespace MinecraftClient.Protocol.Handlers //Process the next packet int packetID = -1; - byte[] packetData = new byte[] { }; + List packetData = new List(); while (true) { - readNextPacket(ref packetID, ref packetData); + readNextPacket(ref packetID, packetData); if (packetID == 0x00) //Login rejected { - handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(ref packetData))); + handler.OnConnectionLost(ChatBot.DisconnectReason.LoginRejected, ChatParser.ParseText(readNextString(packetData))); return false; } else if (packetID == 0x02) //Login successful @@ -1101,10 +1265,10 @@ namespace MinecraftClient.Protocol.Handlers int packetLength = ComTmp.readNextVarIntRAW(); if (packetLength > 0) //Read Response length { - byte[] packetData = ComTmp.readDataRAW(packetLength); - if (readNextVarInt(ref packetData) == 0x00) //Read Packet ID + List packetData = new List(ComTmp.readDataRAW(packetLength)); + if (readNextVarInt(packetData) == 0x00) //Read Packet ID { - string result = readNextString(ref packetData); //Get the Json data + string result = readNextString(packetData); //Get the Json data if (!String.IsNullOrEmpty(result) && result.StartsWith("{") && result.EndsWith("}")) { diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 5376e31a..b9de09d8 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -24,6 +24,7 @@ namespace MinecraftClient.Protocol string GetSessionID(); string[] GetOnlinePlayers(); Location GetCurrentLocation(); + World GetWorld(); /// /// Called when a server was successfully joined diff --git a/MinecraftClient/Settings.cs b/MinecraftClient/Settings.cs index a0c6fc5d..437cbbab 100644 --- a/MinecraftClient/Settings.cs +++ b/MinecraftClient/Settings.cs @@ -409,7 +409,7 @@ namespace MinecraftClient + "chatbotlogfile= #leave empty for no logfile\r\n" + "showsystemmessages=true #system messages for server ops\r\n" + "showxpbarmessages=true #messages displayed above xp bar\r\n" - + "handleterrainandmovements=false #requires more ram and cpu\r\n" + + "handleterrainandmovements=false #requires quite more ram\r\n" + "accountlist=accounts.txt\r\n" + "serverlist=servers.txt\r\n" + "playerheadicon=true\r\n"