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"