Merge pull request #160 from Pokechu22/1.9-terrain-and-movement

Implementation of terrain and movement for 1.9 by Pokechu22
This commit is contained in:
ORelio 2016-06-11 10:59:43 +02:00 committed by GitHub
commit 734abe2296

View file

@ -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<byte> 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<ushort> queue = new Queue<ushort>(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<ushort> queue = new Queue<ushort>(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
}
/// <summary>
/// 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
/// </summary>
/// <returns>The unsigned short integer value</returns>
/// <returns>The unsigned long integer value</returns>
private static ulong readNextULong(List<byte> cache)
{
@ -805,6 +907,20 @@ namespace MinecraftClient.Protocol.Handlers
return readData(len, cache);
}
/// <summary>
/// Reads a length-prefixed array of unsigned long integers and removes it from the cache
/// </summary>
/// <returns>The unsigned long integer values</returns>
private static ulong[] readNextULongArray(List<byte> cache)
{
int len = readNextVarInt(cache);
ulong[] result = new ulong[len];
for (int i = 0; i < len; i++)
result[i] = readNextULong(cache);
return result;
}
/// <summary>
/// Read a double from a cache of bytes and remove it from the cache
/// </summary>