1.16+ Terrain and Movement support (#1353)

* First implementation
* Improve chunk reading performance
* Fix indentation
* Remove debug information
* Update MultiBlockChange packet
* Move skip varint to a method
* Fix crash when not using block palette
* Fix DataTypes.cs not compiling on .NET 4.0
Binary (0b) values not handled so converted to Hexadecimal (0x)
* Use the 1.16 chunk parsing code for 1.15 too
Document the differences in padding and factor the code
Co-authored-by: ORelio <ORelio@users.noreply.github.com>
This commit is contained in:
ReinforceZwei 2020-11-29 03:48:35 +08:00 committed by GitHub
parent 9b5fde0689
commit 53bd56100f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 1505 additions and 44 deletions

File diff suppressed because it is too large Load diff

View file

@ -48,6 +48,7 @@
GoldOre, GoldOre,
IronOre, IronOre,
CoalOre, CoalOre,
NetherGoldOre,
OakLog, OakLog,
SpruceLog, SpruceLog,
BirchLog, BirchLog,
@ -157,6 +158,7 @@
Torch, Torch,
WallTorch, WallTorch,
Fire, Fire,
SoulFire,
Spawner, Spawner,
OakStairs, OakStairs,
Chest, Chest,
@ -207,6 +209,11 @@
Pumpkin, Pumpkin,
Netherrack, Netherrack,
SoulSand, SoulSand,
SoulSoil,
Basalt,
PolishedBasalt,
SoulTorch,
SoulWallTorch,
Glowstone, Glowstone,
NetherPortal, NetherPortal,
CarvedPumpkin, CarvedPumpkin,
@ -249,6 +256,7 @@
RedMushroomBlock, RedMushroomBlock,
MushroomStem, MushroomStem,
IronBars, IronBars,
Chain,
GlassPane, GlassPane,
Melon, Melon,
AttachedPumpkinStem, AttachedPumpkinStem,
@ -685,14 +693,89 @@
Stonecutter, Stonecutter,
Bell, Bell,
Lantern, Lantern,
SoulLantern,
Campfire, Campfire,
SoulCampfire,
SweetBerryBush, SweetBerryBush,
WarpedStem,
StrippedWarpedStem,
WarpedHyphae,
StrippedWarpedHyphae,
WarpedNylium,
WarpedFungus,
WarpedWartBlock,
WarpedRoots,
NetherSprouts,
CrimsonStem,
StrippedCrimsonStem,
CrimsonHyphae,
StrippedCrimsonHyphae,
CrimsonNylium,
CrimsonFungus,
Shroomlight,
WeepingVines,
WeepingVinesPlant,
TwistingVines,
TwistingVinesPlant,
CrimsonRoots,
CrimsonPlanks,
WarpedPlanks,
CrimsonSlab,
WarpedSlab,
CrimsonPressurePlate,
WarpedPressurePlate,
CrimsonFence,
WarpedFence,
CrimsonTrapdoor,
WarpedTrapdoor,
CrimsonFenceGate,
WarpedFenceGate,
CrimsonStairs,
WarpedStairs,
CrimsonButton,
WarpedButton,
CrimsonDoor,
WarpedDoor,
CrimsonSign,
WarpedSign,
CrimsonWallSign,
WarpedWallSign,
StructureBlock, StructureBlock,
Jigsaw, Jigsaw,
Composter, Composter,
Target,
BeeNest, BeeNest,
Beehive, Beehive,
HoneyBlock, HoneyBlock,
HoneycombBlock, HoneycombBlock,
NetheriteBlock,
AncientDebris,
CryingObsidian,
RespawnAnchor,
PottedCrimsonFungus,
PottedWarpedFungus,
PottedCrimsonRoots,
PottedWarpedRoots,
Lodestone,
Blackstone,
BlackstoneStairs,
BlackstoneWall,
BlackstoneSlab,
PolishedBlackstone,
PolishedBlackstoneBricks,
CrackedPolishedBlackstoneBricks,
ChiseledPolishedBlackstone,
PolishedBlackstoneBrickSlab,
PolishedBlackstoneBrickStairs,
PolishedBlackstoneBrickWall,
GildedBlackstone,
PolishedBlackstoneStairs,
PolishedBlackstoneSlab,
PolishedBlackstonePressurePlate,
PolishedBlackstoneButton,
PolishedBlackstoneWall,
ChiseledNetherBricks,
CrackedNetherBricks,
QuartzBricks,
} }
} }

View file

@ -141,6 +141,7 @@
<Compile Include="Mapping\BlockPalettes\Palette115.cs" /> <Compile Include="Mapping\BlockPalettes\Palette115.cs" />
<Compile Include="Mapping\BlockPalettes\BlockPaletteGenerator.cs" /> <Compile Include="Mapping\BlockPalettes\BlockPaletteGenerator.cs" />
<Compile Include="Mapping\BlockPalettes\BlockPalette.cs" /> <Compile Include="Mapping\BlockPalettes\BlockPalette.cs" />
<Compile Include="Mapping\BlockPalettes\Palette116.cs" />
<Compile Include="Mapping\CommandBlockFlags.cs" /> <Compile Include="Mapping\CommandBlockFlags.cs" />
<Compile Include="Mapping\CommandBlockMode.cs" /> <Compile Include="Mapping\CommandBlockMode.cs" />
<Compile Include="Mapping\Entity.cs" /> <Compile Include="Mapping\Entity.cs" />
@ -401,4 +402,4 @@
<Target Name="AfterBuild"> <Target Name="AfterBuild">
</Target> </Target>
--> -->
</Project> </Project>

View file

@ -267,6 +267,19 @@ namespace MinecraftClient.Protocol.Handlers
return i; return i;
} }
/// <summary>
/// Skip a VarInt from a cache of bytes with better performance
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
public void SkipNextVarInt(Queue<byte> cache)
{
while (true)
{
if ((ReadNextByte(cache) & 0x80) != 128)
break;
}
}
/// <summary> /// <summary>
/// Read an "extended short", which is actually an int of some kind, from the cache of bytes. /// Read an "extended short", which is actually an int of some kind, from the cache of bytes.
/// This is only done with forge. It looks like it's a normal short, except that if the high /// This is only done with forge. It looks like it's a normal short, except that if the high
@ -286,6 +299,31 @@ namespace MinecraftClient.Protocol.Handlers
return ((high & 0xFF) << 15) | low; return ((high & 0xFF) << 15) | low;
} }
/// <summary>
/// Read a long from a cache of bytes and remove it from the cache
/// </summary>
/// <param name="cache">Cache of bytes to read from</param>
/// <returns>The long value</returns>
public long ReadNextVarLong(Queue<byte> cache)
{
int numRead = 0;
long result = 0;
byte read;
do
{
read = ReadNextByte(cache);
long value = (read & 0x7F);
result |= (value << (7 * numRead));
numRead++;
if (numRead > 10)
{
throw new OverflowException("VarLong is too big");
}
} while ((read & 0x80) != 0);
return result;
}
/// <summary> /// <summary>
/// Read a single byte from a cache of bytes and remove it from the cache /// Read a single byte from a cache of bytes and remove it from the cache
/// </summary> /// </summary>

View file

@ -80,7 +80,7 @@ namespace MinecraftClient.Protocol.Handlers
this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler); this.pTerrain = new Protocol18Terrain(protocolVersion, dataTypes, handler);
this.packetPalette = new PacketTypeHandler(protocolVersion).GetTypeHandler(); this.packetPalette = new PacketTypeHandler(protocolVersion).GetTypeHandler();
if (handler.GetTerrainEnabled() && protocolversion > MC1152Version) if (handler.GetTerrainEnabled() && protocolversion > MC1164Version)
{ {
Translations.WriteLineFormatted("extra.terrainandmovement_disabled"); Translations.WriteLineFormatted("extra.terrainandmovement_disabled");
handler.SetTerrainEnabled(false); handler.SetTerrainEnabled(false);
@ -101,9 +101,11 @@ namespace MinecraftClient.Protocol.Handlers
// Block palette // Block palette
if (protocolversion >= MC113Version) if (protocolversion >= MC113Version)
{ {
if (protocolVersion > MC1152Version && handler.GetTerrainEnabled()) if (protocolVersion > MC1164Version && handler.GetTerrainEnabled())
throw new NotImplementedException(Translations.Get("exception.palette.block")); throw new NotImplementedException(Translations.Get("exception.palette.block"));
if (protocolVersion >= MC115Version) if (protocolVersion >= MC116Version)
Block.Palette = new Palette116();
else if (protocolVersion >= MC115Version)
Block.Palette = new Palette115(); Block.Palette = new Palette115();
else if (protocolVersion >= MC114Version) else if (protocolVersion >= MC114Version)
Block.Palette = new Palette114(); Block.Palette = new Palette114();
@ -398,6 +400,8 @@ namespace MinecraftClient.Protocol.Handlers
int chunkX = dataTypes.ReadNextInt(packetData); int chunkX = dataTypes.ReadNextInt(packetData);
int chunkZ = dataTypes.ReadNextInt(packetData); int chunkZ = dataTypes.ReadNextInt(packetData);
bool chunksContinuous = dataTypes.ReadNextBool(packetData); bool chunksContinuous = dataTypes.ReadNextBool(packetData);
if (protocolversion >= MC116Version && protocolversion <= MC1161Version)
dataTypes.ReadNextBool(packetData); // Ignore old data - 1.16 to 1.16.1 only
ushort chunkMask = protocolversion >= MC19Version ushort chunkMask = protocolversion >= MC19Version
? (ushort)dataTypes.ReadNextVarInt(packetData) ? (ushort)dataTypes.ReadNextVarInt(packetData)
: dataTypes.ReadNextUShort(packetData); : dataTypes.ReadNextUShort(packetData);
@ -413,8 +417,23 @@ namespace MinecraftClient.Protocol.Handlers
{ {
if (protocolversion >= MC114Version) if (protocolversion >= MC114Version)
dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above dataTypes.ReadNextNbt(packetData); // Heightmaps - 1.14 and above
int biomesLength = 0;
if (protocolversion >= MC1162Version)
if (chunksContinuous)
biomesLength = dataTypes.ReadNextVarInt(packetData); // Biomes length - 1.16.2 and above
if (protocolversion >= MC115Version && chunksContinuous) if (protocolversion >= MC115Version && chunksContinuous)
dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above {
if (protocolversion >= MC1162Version)
{
for (int i = 0; i < biomesLength; i++)
{
// Biomes - 1.16.2 and above
// Don't use ReadNextVarInt because it cost too much time
dataTypes.SkipNextVarInt(packetData);
}
}
else dataTypes.ReadData(1024 * 4, packetData); // Biomes - 1.15 and above
}
int dataSize = dataTypes.ReadNextVarInt(packetData); int dataSize = dataTypes.ReadNextVarInt(packetData);
pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData);
} }
@ -514,35 +533,62 @@ namespace MinecraftClient.Protocol.Handlers
case PacketTypesIn.MultiBlockChange: case PacketTypesIn.MultiBlockChange:
if (handler.GetTerrainEnabled()) if (handler.GetTerrainEnabled())
{ {
int chunkX = dataTypes.ReadNextInt(packetData); if (protocolversion >= MC1162Version)
int chunkZ = dataTypes.ReadNextInt(packetData);
int recordCount = protocolversion < MC18Version
? (int)dataTypes.ReadNextShort(packetData)
: dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < recordCount; i++)
{ {
byte locationXZ; long chunkSection = dataTypes.ReadNextLong(packetData);
ushort blockIdMeta; int sectionX = (int)(chunkSection >> 42);
int blockY; int sectionY = (int)((chunkSection << 44) >> 44);
int sectionZ = (int)((chunkSection << 22) >> 42);
if (protocolversion < MC18Version) dataTypes.ReadNextBool(packetData); // Useless boolean
int blocksSize = dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < blocksSize; i++)
{ {
blockIdMeta = dataTypes.ReadNextUShort(packetData); ulong block = (ulong)dataTypes.ReadNextVarLong(packetData);
blockY = (ushort)dataTypes.ReadNextByte(packetData); int blockId = (int)(block >> 12);
locationXZ = dataTypes.ReadNextByte(packetData); int localX = (int)((block >> 8) & 0x0F);
} int localZ = (int)((block >> 4) & 0x0F);
else int localY = (int)(block & 0x0F);
{
locationXZ = dataTypes.ReadNextByte(packetData);
blockY = (ushort)dataTypes.ReadNextByte(packetData);
blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData);
}
int blockX = locationXZ >> 4; Block b = new Block((ushort)blockId);
int blockZ = locationXZ & 0x0F; int blockX = (sectionX * 16) + localX;
Block block = new Block(blockIdMeta); int blockY = (sectionY * 16) + localY;
handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block); int blockZ = (sectionZ * 16) + localZ;
var l = new Location(blockX, blockY, blockZ);
handler.GetWorld().SetBlock(l, b);
}
}
else
{
int chunkX = dataTypes.ReadNextInt(packetData);
int chunkZ = dataTypes.ReadNextInt(packetData);
int recordCount = protocolversion < MC18Version
? (int)dataTypes.ReadNextShort(packetData)
: dataTypes.ReadNextVarInt(packetData);
for (int i = 0; i < recordCount; i++)
{
byte locationXZ;
ushort blockIdMeta;
int blockY;
if (protocolversion < MC18Version)
{
blockIdMeta = dataTypes.ReadNextUShort(packetData);
blockY = (ushort)dataTypes.ReadNextByte(packetData);
locationXZ = dataTypes.ReadNextByte(packetData);
}
else
{
locationXZ = dataTypes.ReadNextByte(packetData);
blockY = (ushort)dataTypes.ReadNextByte(packetData);
blockIdMeta = (ushort)dataTypes.ReadNextVarInt(packetData);
}
int blockX = locationXZ >> 4;
int blockZ = locationXZ & 0x0F;
Block block = new Block(blockIdMeta);
handler.GetWorld().SetBlock(new Location(chunkX, chunkZ, blockX, blockY, blockZ), block);
}
} }
} }
break; break;

View file

@ -75,43 +75,86 @@ namespace MinecraftClient.Protocol.Handlers
// EG, if bitsPerBlock = 5, valueMask = 00011111 in binary // EG, if bitsPerBlock = 5, valueMask = 00011111 in binary
uint valueMask = (uint)((1 << bitsPerBlock) - 1); uint valueMask = (uint)((1 << bitsPerBlock) - 1);
// Block IDs are packed in the array of 64-bits integers
ulong[] dataArray = dataTypes.ReadNextULongArray(cache); ulong[] dataArray = dataTypes.ReadNextULongArray(cache);
Chunk chunk = new Chunk(); Chunk chunk = new Chunk();
if (dataArray.Length > 0) if (dataArray.Length > 0)
{ {
int longIndex = 0;
int startOffset = 0 - bitsPerBlock;
for (int blockY = 0; blockY < Chunk.SizeY; blockY++) for (int blockY = 0; blockY < Chunk.SizeY; blockY++)
{ {
for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++)
{ {
for (int blockX = 0; blockX < Chunk.SizeX; blockX++) for (int blockX = 0; blockX < Chunk.SizeX; blockX++)
{ {
int blockNumber = (blockY * Chunk.SizeZ + blockZ) * Chunk.SizeX + blockX; // NOTICE: In the future a single ushort may not store the entire block id;
// the Block class may need to change if block state IDs go beyond 65535
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 if block state IDs go beyond 65535
ushort blockId; ushort blockId;
if (startLong == endLong)
// Calculate location of next block ID inside the array of Longs
startOffset += bitsPerBlock;
bool overlap = false;
if ((startOffset + bitsPerBlock) > 64)
{ {
blockId = (ushort)((dataArray[startLong] >> startOffset) & valueMask); if (protocolversion >= Protocol18Handler.MC116Version)
{
// In MC 1.16+, padding is applied to prevent overlapping between Longs:
// [ LONG INTEGER ][ LONG INTEGER ]
// [Block][Block][Block]XXXXX[Block][Block][Block]XXXXX
// When overlapping, move forward to the beginning of the next Long
startOffset = 0;
longIndex++;
}
else
{
// In MC 1.15 and lower, block IDs can overlap between Longs:
// [ LONG INTEGER ][ LONG INTEGER ]
// [Block][Block][Block][Blo ck][Block][Block][Block][
// Detect when we reached the next Long or switch to overlap mode
if (startOffset >= 64)
{
startOffset -= 64;
longIndex++;
}
else overlap = true;
}
}
// Extract Block ID
if (overlap)
{
int endOffset = 64 - startOffset;
blockId = (ushort)((dataArray[longIndex] >> startOffset | dataArray[longIndex + 1] << endOffset) & valueMask);
} }
else else
{ {
int endOffset = 64 - startOffset; blockId = (ushort)((dataArray[longIndex] >> startOffset) & valueMask);
blockId = (ushort)((dataArray[startLong] >> startOffset | dataArray[endLong] << endOffset) & valueMask);
} }
// Map small IDs to actual larger block IDs
if (usePalette) if (usePalette)
{ {
// Get the real block ID out of the palette if (paletteLength <= blockId)
{
int blockNumber = (blockY * Chunk.SizeZ + blockZ) * Chunk.SizeX + blockX;
throw new IndexOutOfRangeException(String.Format("Block ID {0} is outside Palette range 0-{1}! (bitsPerBlock: {2}, blockNumber: {3})",
blockId,
paletteLength - 1,
bitsPerBlock,
blockNumber));
}
blockId = (ushort)palette[blockId]; blockId = (ushort)palette[blockId];
} }
// We have our block, save the block into the chunk
chunk[blockX, blockY, blockZ] = new Block(blockId); chunk[blockX, blockY, blockZ] = new Block(blockId);
} }
} }