From 2dae739b74128d98c884c2415d88b51e5b77b369 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Fri, 22 Jul 2022 20:09:53 +0800 Subject: [PATCH 01/15] Update to .NET 4.8 --- MinecraftClient/MinecraftClient.csproj | 10 +++++++--- MinecraftClient/app.config | 3 +++ 2 files changed, 10 insertions(+), 3 deletions(-) create mode 100644 MinecraftClient/app.config diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index d104601e..44aec940 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -1,5 +1,5 @@  - + Debug x86 @@ -10,8 +10,9 @@ Properties MinecraftClient MinecraftClient - v4.0 - Client + v4.8 + + 512 publish\ true @@ -38,6 +39,7 @@ DEBUG;TRACE prompt 4 + false x86 @@ -47,6 +49,7 @@ TRACE prompt 4 + false @@ -418,6 +421,7 @@ + diff --git a/MinecraftClient/app.config b/MinecraftClient/app.config new file mode 100644 index 00000000..3e0e37cf --- /dev/null +++ b/MinecraftClient/app.config @@ -0,0 +1,3 @@ + + + From f9bb74a8bd3b0a6760887717cc9529be878806c0 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sat, 23 Jul 2022 22:34:16 +0800 Subject: [PATCH 02/15] Implemented ChunkData packet process for 1.17/1.17.1 --- MinecraftClient/Mapping/ChunkColumn.cs | 13 +- .../Protocol/Handlers/Protocol18.cs | 121 ++++++++----- .../Protocol/Handlers/Protocol18Terrain.cs | 170 +++++++++++++++++- 3 files changed, 255 insertions(+), 49 deletions(-) diff --git a/MinecraftClient/Mapping/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs index 22c9d2ae..a7e704ce 100644 --- a/MinecraftClient/Mapping/ChunkColumn.cs +++ b/MinecraftClient/Mapping/ChunkColumn.cs @@ -11,18 +11,27 @@ namespace MinecraftClient.Mapping /// public class ChunkColumn { - public const int ColumnSize = 16; + public int ColumnSize; /// /// Blocks contained into the chunk /// - private readonly Chunk[] chunks = new Chunk[ColumnSize]; + private readonly Chunk[] chunks; /// /// Lock for thread safety /// private readonly ReaderWriterLockSlim chunkLock = new ReaderWriterLockSlim(); + /// + /// Create a new ChunkColumn + /// + public ChunkColumn(int size = 16) + { + ColumnSize = size; + chunks = new Chunk[size]; + } + /// /// Get or set the specified chunk column /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index f4cb507f..a276ee7a 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -111,7 +111,7 @@ namespace MinecraftClient.Protocol.Handlers { if (protocolVersion > MC1182Version && handler.GetTerrainEnabled()) throw new NotImplementedException(Translations.Get("exception.palette.block")); - if (protocolVersion >= MC1181Version) + if (protocolVersion >= MC117Version) Block.Palette = new Palette117(); else if (protocolVersion >= MC116Version) Block.Palette = new Palette116(); @@ -425,56 +425,77 @@ namespace MinecraftClient.Protocol.Handlers SendPacket(PacketTypesOut.TeleportConfirm, dataTypes.GetVarInt(teleportID)); } - if (protocolversion >= MC117Version) dataTypes.ReadNextBool(packetData); + if (protocolversion >= MC117Version) + dataTypes.ReadNextBool(packetData); // Dismount Vehicle - 1.17 and above break; - case PacketTypesIn.ChunkData: //TODO implement for 1.17, bit mask is not limited to 0-15 anymore + case PacketTypesIn.ChunkData: if (handler.GetTerrainEnabled()) { int chunkX = dataTypes.ReadNextInt(packetData); int chunkZ = dataTypes.ReadNextInt(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)dataTypes.ReadNextVarInt(packetData) - : dataTypes.ReadNextUShort(packetData); - if (protocolversion < MC18Version) + if (protocolversion >= MC117Version) { - ushort addBitmap = dataTypes.ReadNextUShort(packetData); - int compressedDataSize = dataTypes.ReadNextInt(packetData); - byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); - byte[] decompressed = ZlibUtils.Decompress(compressed); + ulong[] verticalStripBitmask = dataTypes.ReadNextULongArray(packetData); // Bit Mask Length and Primary Bit Mask + dataTypes.ReadNextNbt(packetData); // Heightmaps + + int biomesLength = dataTypes.ReadNextVarInt(packetData); // Biomes length + for (int i = 0; i < biomesLength; i++) + { + dataTypes.SkipNextVarInt(packetData); // Biomes + } + + int dataSize = dataTypes.ReadNextVarInt(packetData); // Size new Task(() => { - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed)); + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, currentDimension, packetData); }).Start(); } else { - if (protocolversion >= MC114Version) - 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) + 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)dataTypes.ReadNextVarInt(packetData) + : dataTypes.ReadNextUShort(packetData); + if (protocolversion < MC18Version) { - if (protocolversion >= MC1162Version) + ushort addBitmap = dataTypes.ReadNextUShort(packetData); + int compressedDataSize = dataTypes.ReadNextInt(packetData); + byte[] compressed = dataTypes.ReadData(compressedDataSize, packetData); + byte[] decompressed = ZlibUtils.Decompress(compressed); + new Task(() => { - 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 + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, addBitmap, currentDimension == 0, chunksContinuous, currentDimension, new Queue(decompressed)); + }).Start(); } - int dataSize = dataTypes.ReadNextVarInt(packetData); - new Task(() => + else { - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); - }).Start(); + if (protocolversion >= MC114Version) + 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 >= 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); + new Task(() => + { + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, chunkMask, 0, false, chunksContinuous, currentDimension, packetData); + }).Start(); + } } } break; @@ -839,7 +860,7 @@ namespace MinecraftClient.Protocol.Handlers int stateId = -1; - if (protocolversion >= MC1181Version) + if (protocolversion >= MC1171Version) // State ID - 1.17.1 and above stateId = dataTypes.ReadNextVarInt(packetData); int elements = dataTypes.ReadNextVarInt(packetData); @@ -851,6 +872,9 @@ namespace MinecraftClient.Protocol.Handlers inventorySlots[slotId] = item; } handler.OnWindowItems(windowId, inventorySlots, stateId); + + if (protocolversion >= MC1171Version) // Carried Item - 1.17.1 and above + dataTypes.ReadNextItemSlot(packetData, itemPalette); } break; case PacketTypesIn.SetSlot: @@ -860,7 +884,7 @@ namespace MinecraftClient.Protocol.Handlers int stateId = -1; - if (protocolversion >= MC1181Version) + if (protocolversion >= MC1171Version) // State ID - 1.17.1 and above stateId = dataTypes.ReadNextVarInt(packetData); short slotID = dataTypes.ReadNextShort(packetData); @@ -888,6 +912,8 @@ namespace MinecraftClient.Protocol.Handlers { forced = dataTypes.ReadNextBool(packetData); String forcedMessage = ChatParser.ParseText(dataTypes.ReadNextString(packetData)); + // skip: Has Prompt Message (Boolean) + // skip: Prompt Message (Optional Chat) } // Some server plugins may send invalid resource packs to probe the client and we need to ignore them (issue #1056) if (!url.StartsWith("http") && hash.Length != 40) // Some server may have null hash value @@ -986,7 +1012,20 @@ namespace MinecraftClient.Protocol.Handlers case PacketTypesIn.DestroyEntity: if (handler.GetEntityHandlingEnabled()) { - handler.OnDestroyEntities(new[] { dataTypes.ReadNextVarInt(packetData) }); + int[] entityIDs; + if (protocolversion == MC117Version) // single Entity ID per packet - 1.17 only + { + entityIDs = new int[1]; + entityIDs[0] = dataTypes.ReadNextVarInt(packetData); + } + else + { + int count = dataTypes.ReadNextVarInt(packetData); + entityIDs = new int[count]; + for (int i = 0; i < count; ++i) + entityIDs[i] = dataTypes.ReadNextVarInt(packetData); + } + handler.OnDestroyEntities(entityIDs); } break; case PacketTypesIn.EntityPosition: @@ -1646,7 +1685,7 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion >= MC19Version) fields.AddRange(dataTypes.GetVarInt(mainHand)); if (protocolversion >= MC117Version) - fields.Add(0); // Enables text filtering. Always false + fields.Add(1); // 1.17 and above - Disable text filtering. (Always true) if (protocolversion >= MC1181Version) fields.Add(1); // 1.18 and above - Allow server listings SendPacket(PacketTypesOut.ClientSettings, fields); @@ -1938,7 +1977,7 @@ namespace MinecraftClient.Protocol.Handlers packet.Add((byte)windowId); // 1.18+ - if (protocolversion > MC1171Version) + if (protocolversion >= MC1181Version) { packet.AddRange(dataTypes.GetVarInt(stateId)); packet.AddRange(dataTypes.GetShort((short)slotId)); @@ -1973,7 +2012,7 @@ namespace MinecraftClient.Protocol.Handlers packet.AddRange(arrayOfSlots); } - packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); + packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); // Clicked item log.Info("Packet data: " + dataTypes.ByteArrayToString(packet.ToArray())); diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index 14202599..5ce8644f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -28,7 +28,164 @@ namespace MinecraftClient.Protocol.Handlers } /// - /// Process chunk column data from the server and (un)load the chunk from the Minecraft world + /// Process chunk column data from the server and (un)load the chunk from the Minecraft world - 1.17 and above + /// + /// Chunk X location + /// Chunk Z location + /// Chunk mask for reading data, store in bitset + /// Current dimension type (0 = overworld) + /// Cache for reading chunk data + public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[] chunkMasks, int currentDimension, Queue cache) + { + byte[] mirror = new byte[cache.Count]; + cache.CopyTo(mirror, 0); + int chunkColumnSize = chunkMasks.Length * 64; + if (protocolversion >= Protocol18Handler.MC117Version) + { + // 1.17 and above chunk format + // Unloading chunks is handled by a separate packet + for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) + { + if ((chunkMasks[chunkY / 64] & (1UL << (chunkY % 64))) != 0) + { + // Non-air block count inside chunk section, for lighting purposes + int blockCnt = dataTypes.ReadNextShort(cache); + + // read Block states (Type: Paletted Container) + Chunk chunk = new Chunk(); + + byte bitsPerEntry = dataTypes.ReadNextByte(cache); + if (bitsPerEntry == 0 && protocolversion >= Protocol18Handler.MC1181Version) + { + // Palettes: Single valued - 1.xx and above + ushort value = (ushort)dataTypes.ReadNextVarInt(cache); + + dataTypes.SkipNextVarInt(cache); // Data Array Length will be zero + + 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(value); + } + } + } + } + else + { + // Palettes: Indirect or Direct + bool usePalette = (bitsPerEntry <= 8); + + // Indirect Mode: For block states with bits per entry <= 4, 4 bits are used to represent a block. + if (bitsPerEntry < 4) bitsPerEntry = 4; + + // Direct Mode: Bit mask covering bitsPerBlock bits + // EG, if bitsPerBlock = 5, valueMask = 00011111 in binary + uint valueMask = (uint)((1 << bitsPerEntry) - 1); + + int paletteLength = 0; // Assume zero when length is absent + if (usePalette) paletteLength = dataTypes.ReadNextVarInt(cache); + + int[] palette = new int[paletteLength]; + for (int i = 0; i < paletteLength; i++) + palette[i] = dataTypes.ReadNextVarInt(cache); + + // Block IDs are packed in the array of 64-bits integers + ulong[] dataArray = dataTypes.ReadNextULongArray(cache); + + int longIndex = 0; + int startOffset = 0 - bitsPerEntry; + for (int blockY = 0; blockY < Chunk.SizeY; blockY++) + { + for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) + { + for (int blockX = 0; blockX < 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 + ushort blockId; + + // Calculate location of next block ID inside the array of Longs + startOffset += bitsPerEntry; + + if ((startOffset + bitsPerEntry) > 64) + { + // 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++; + } + + // Extract Block ID + blockId = (ushort)((dataArray[longIndex] >> startOffset) & valueMask); + + // Map small IDs to actual larger block IDs + if (usePalette) + { + 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, + bitsPerEntry, + blockNumber)); + } + + blockId = (ushort)palette[blockId]; + } + + // We have our block, save the block into the chunk + chunk[blockX, blockY, blockZ] = new Block(blockId); + } + } + } + } + + //We have our chunk, save the chunk into the world + handler.InvokeOnMainThread(() => + { + if (handler.GetWorld()[chunkX, chunkZ] == null) + handler.GetWorld()[chunkX, chunkZ] = new ChunkColumn(chunkColumnSize); + handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk; + }); + + if (protocolversion >= Protocol18Handler.MC1181Version) + { + // skip read Biomes (Type: Paletted Container) + byte bitsPerEntryBiome = dataTypes.ReadNextByte(cache); // Bits Per Entry + if (bitsPerEntryBiome == 0 && protocolversion >= Protocol18Handler.MC1181Version) + { + dataTypes.SkipNextVarInt(cache); // Value + dataTypes.SkipNextVarInt(cache); // Data Array Length + // Data Array must be empty + } + else + { + if (bitsPerEntryBiome <= 3) + { + int paletteLength = dataTypes.ReadNextVarInt(cache); // Palette Length + for (int i = 0; i < paletteLength; i++) + dataTypes.SkipNextVarInt(cache); // Palette + } + int dataArrayLength = dataTypes.ReadNextVarInt(cache); // Data Array Length + dataTypes.ReadData(dataArrayLength * 8, cache); // Data Array + } + } + } + } + // 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) + } + } + + /// + /// Process chunk column data from the server and (un)load the chunk from the Minecraft world - 1.17 below /// /// Chunk X location /// Chunk Z location @@ -40,11 +197,12 @@ namespace MinecraftClient.Protocol.Handlers /// Cache for reading chunk data public void ProcessChunkColumnData(int chunkX, int chunkZ, ushort chunkMask, ushort chunkMask2, bool hasSkyLight, bool chunksContinuous, int currentDimension, Queue cache) { + const int chunkColumnSize = 16; if (protocolversion >= Protocol18Handler.MC19Version) { // 1.9 and above chunk format // Unloading chunks is handled by a separate packet - for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) { if ((chunkMask & (1 << chunkY)) != 0) { @@ -200,7 +358,7 @@ namespace MinecraftClient.Protocol.Handlers else { //Load chunk data from the server - for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) { if ((chunkMask & (1 << chunkY)) != 0) { @@ -224,7 +382,7 @@ namespace MinecraftClient.Protocol.Handlers } //Skip light information - for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) { if ((chunkMask & (1 << chunkY)) != 0) { @@ -258,7 +416,7 @@ namespace MinecraftClient.Protocol.Handlers //Count chunk sections int sectionCount = 0; int addDataSectionCount = 0; - for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) { if ((chunkMask & (1 << chunkY)) != 0) sectionCount++; @@ -286,7 +444,7 @@ namespace MinecraftClient.Protocol.Handlers dataTypes.ReadData(Chunk.SizeX * Chunk.SizeZ, cache); //Biomes //Load chunk data - for (int chunkY = 0; chunkY < ChunkColumn.ColumnSize; chunkY++) + for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) { if ((chunkMask & (1 << chunkY)) != 0) { From af574b654eb078e05856bd415057f3474af839cf Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sat, 23 Jul 2022 23:10:06 +0800 Subject: [PATCH 03/15] avoid pathfind to an unloaded chunk | remove debug logs --- MinecraftClient/Commands/Move.cs | 4 +++- MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs | 2 -- MinecraftClient/Resources/lang/en.ini | 1 + 3 files changed, 4 insertions(+), 3 deletions(-) diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 0af06344..56fde8e3 100644 --- a/MinecraftClient/Commands/Move.cs +++ b/MinecraftClient/Commands/Move.cs @@ -76,7 +76,9 @@ namespace MinecraftClient.Commands int z = int.Parse(args[2]); Location goal = new Location(x, y, z); - if (handler.MoveTo(goal, allowUnsafe: takeRisk)) + if (handler.GetWorld().GetChunkColumn(goal) == null) + return Translations.Get("cmd.move.chunk_not_loaded"); + else if (handler.MoveTo(goal, allowUnsafe: takeRisk)) return Translations.Get("cmd.move.walk", goal); else return takeRisk ? Translations.Get("cmd.move.fail", goal) : Translations.Get("cmd.move.suggestforce", goal); } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index 5ce8644f..0a932bca 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -37,8 +37,6 @@ namespace MinecraftClient.Protocol.Handlers /// Cache for reading chunk data public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[] chunkMasks, int currentDimension, Queue cache) { - byte[] mirror = new byte[cache.Count]; - cache.CopyTo(mirror, 0); int chunkColumnSize = chunkMasks.Length * 64; if (protocolversion >= Protocol18Handler.MC117Version) { diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index c6db23d3..e1a72f12 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -319,6 +319,7 @@ cmd.move.fail=Failed to compute path to {0} cmd.move.suggestforce=Failed to compute a safe path to {0}. Try -f parameter to allow unsafe movements. cmd.move.gravity.enabled=Gravity is enabled. cmd.move.gravity.disabled=Gravity is disabled. +cmd.move.chunk_not_loaded=The chunk where the target location resides has not yet been loaded. # Reco cmd.reco.desc=restart and reconnect to the server. From 516effa81d25bc2dfad990369c4287bca251b7c1 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sun, 24 Jul 2022 21:41:56 +0800 Subject: [PATCH 04/15] terrain handling for 1.18(1.18.1) and 1.18.2 --- MinecraftClient/Mapping/Dimension.cs | 169 +++++++++++++ MinecraftClient/Mapping/Location.cs | 10 +- MinecraftClient/Mapping/World.cs | 30 +++ MinecraftClient/MinecraftClient.csproj | 1 + .../Protocol/Handlers/Protocol18.cs | 65 +++-- .../Protocol/Handlers/Protocol18Terrain.cs | 229 ++++++++++-------- 6 files changed, 384 insertions(+), 120 deletions(-) create mode 100644 MinecraftClient/Mapping/Dimension.cs diff --git a/MinecraftClient/Mapping/Dimension.cs b/MinecraftClient/Mapping/Dimension.cs new file mode 100644 index 00000000..0ca08e6e --- /dev/null +++ b/MinecraftClient/Mapping/Dimension.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace MinecraftClient.Mapping +{ + + /// + /// The dimension type, available after 1.16.2 + /// + public class Dimension + { + /// + /// The name of the dimension type (for example, "minecraft:overworld"). + /// + public readonly string Name; + + /// + /// Whether piglins shake and transform to zombified piglins. + /// + public readonly bool piglin_safe; + + /// + /// When false, compasses spin randomly. When true, nether portals can spawn zombified piglins. + /// + public readonly bool natural; + + /// + /// How much light the dimension has. + /// + public readonly float ambient_light; + + + /// + /// If set, the time of the day is the specified value. + /// Value: -1: not set + /// Value: [0, 24000]: time of the day + /// + public readonly long fixed_time = -1; + + /// + /// A resource location defining what block tag to use for infiniburn. + /// Value: "" or minecraft resource "minecraft:...". + /// + public readonly string infiniburn; + + /// + /// Whether players can charge and use respawn anchors. + /// + public readonly bool respawn_anchor_works; + + /// + /// Whether the dimension has skylight access or not. + /// + public readonly bool has_skylight; + + /// + /// Whether players can use a bed to sleep. + /// + public readonly bool bed_works; + + /// + /// unknown + /// Values: "minecraft:overworld", "minecraft:the_nether", "minecraft:the_end" or something else. + /// + public readonly string effects; + + /// + /// Whether players with the Bad Omen effect can cause a raid. + /// + public readonly bool has_raids; + + /// + /// The minimum Y level. + /// + public readonly int min_y = 0; + + /// + /// The minimum Y level. + /// + public readonly int max_y = 256; + + /// + /// The maximum height. + /// + public readonly int height = 256; + + /// + /// The maximum height to which chorus fruits and nether portals can bring players within this dimension. + /// + public readonly int logical_height; + + /// + /// The multiplier applied to coordinates when traveling to the dimension. + /// + public readonly double coordinate_scale; + + /// + /// Whether the dimensions behaves like the nether (water evaporates and sponges dry) or not. Also causes lava to spread thinner. + /// + public readonly bool ultrawarm; + + /// + /// Whether the dimension has a bedrock ceiling or not. When true, causes lava to spread faster. + /// + public readonly bool has_ceiling; + + + /// + /// Create from the "Dimension Codec" NBT Tag Compound + /// + /// ChunkColumn X + /// ChunkColumn Y + /// chunk at the given location + public Dimension(string name, Dictionary nbt) + { + if (name == null) + throw new ArgumentNullException("name"); + if (nbt == null) + throw new ArgumentNullException("nbt Data"); + + this.Name = name; + + if (nbt.ContainsKey("piglin_safe")) + this.piglin_safe = 1 == (byte)nbt["piglin_safe"]; + if (nbt.ContainsKey("natural")) + this.natural = 1 == (byte)nbt["natural"]; + if (nbt.ContainsKey("ambient_light")) + this.ambient_light = (float)nbt["ambient_light"]; + if (nbt.ContainsKey("fixed_time")) + this.fixed_time = (long)nbt["fixed_time"]; + if (nbt.ContainsKey("infiniburn")) + this.infiniburn = (string)nbt["infiniburn"]; + if (nbt.ContainsKey("respawn_anchor_works")) + this.respawn_anchor_works = 1 == (byte)nbt["respawn_anchor_works"]; + if (nbt.ContainsKey("has_skylight")) + this.has_skylight = 1 == (byte)nbt["has_skylight"]; + if (nbt.ContainsKey("bed_works")) + this.bed_works = 1 == (byte)nbt["bed_works"]; + if (nbt.ContainsKey("effects")) + this.effects = (string)nbt["effects"]; + if (nbt.ContainsKey("has_raids")) + this.has_raids = 1 == (byte)nbt["has_raids"]; + if (nbt.ContainsKey("min_y")) + this.min_y = (int)nbt["min_y"]; + if (nbt.ContainsKey("height")) + this.height = (int)nbt["height"]; + if (nbt.ContainsKey("min_y") && nbt.ContainsKey("height")) + this.max_y = this.min_y + this.height; + if (nbt.ContainsKey("logical_height")) + this.logical_height = (int)nbt["logical_height"]; + if (nbt.ContainsKey("coordinate_scale")) + { + var coordinate_scale_obj = nbt["coordinate_scale"]; + if (coordinate_scale_obj.GetType() == typeof(float)) + this.coordinate_scale = (float)coordinate_scale_obj; + else + this.coordinate_scale = (double)coordinate_scale_obj; + } + if (nbt.ContainsKey("ultrawarm")) + this.ultrawarm = 1 == (byte)nbt["ultrawarm"]; + if (nbt.ContainsKey("has_ceiling")) + this.has_ceiling = 1 == (byte)nbt["has_ceiling"]; + } + + } +} diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index 00f18071..ad02d75a 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -25,6 +25,11 @@ namespace MinecraftClient.Mapping /// public double Z; + /// + /// Current world: to get the lowest Y coordinate + /// + public static World world; + /// /// Get location with zeroed coordinates /// @@ -79,7 +84,10 @@ namespace MinecraftClient.Mapping { get { - return (int)Math.Floor(Y / Chunk.SizeY); + if (world.GetDimension() == null) + return (int)Math.Floor(Y / Chunk.SizeY); // old version, always start at zero + else + return (int)Math.Floor((Y - world.GetDimension().min_y) / Chunk.SizeY); } } diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs index 9a57baae..b324007a 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -16,6 +16,11 @@ namespace MinecraftClient.Mapping /// private Dictionary> chunks = new Dictionary>(); + /// + /// The dimension info of the world + /// + private Dimension dimension; + /// /// Lock for thread safety /// @@ -78,6 +83,30 @@ namespace MinecraftClient.Mapping } } + public World() + { + Location.world = this; + } + + /// + /// Set dimension type + /// + /// The name of the dimension type + /// The dimension type (NBT Tag Compound) + public void SetDimension(string name, Dictionary nbt) + { + this.dimension = new Dimension(name, nbt); + } + + /// + /// Get dimension type + /// + /// The chunk column + public Dimension GetDimension() + { + return this.dimension; + } + /// /// Get chunk column at the specified location /// @@ -176,6 +205,7 @@ namespace MinecraftClient.Mapping try { chunks = new Dictionary>(); + dimension = null; } finally { diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 44aec940..86351c15 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -79,6 +79,7 @@ + diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index a276ee7a..a55a214c 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -302,17 +302,23 @@ namespace MinecraftClient.Protocol.Handlers for (int i = 0; i < worldCount; i++) dataTypes.ReadNextString(packetData); // World Names - 1.16 and above dataTypes.ReadNextNbt(packetData); // Dimension Codec - 1.16 and above + } - //Current dimension - String identifier in 1.16, varInt below 1.16, byte below 1.9.1 + string currentDimensionName = null; + Dictionary currentDimensionType = null; + + // Current dimension + // NBT Tag Compound: 1.16.2 and above + // String identifier: 1.16 and 1.16.1 + // varInt: [1.9.1 to 1.15.2] + // byte: below 1.9.1 if (protocolversion >= MC116Version) { if (protocolversion >= MC1162Version) - dataTypes.ReadNextNbt(packetData); + currentDimensionType = dataTypes.ReadNextNbt(packetData); else dataTypes.ReadNextString(packetData); - // TODO handle dimensions for 1.16+, needed for terrain handling - // TODO this data give min and max y which will be needed for chunk collumn handling this.currentDimension = 0; } else if (protocolversion >= MC191Version) @@ -322,8 +328,16 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion < MC114Version) dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below + if (protocolversion >= MC116Version) - dataTypes.ReadNextString(packetData); // World Name - 1.16 and above + currentDimensionName = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above + + if (protocolversion >= MC1162Version) + new Task(() => + { + handler.GetWorld().SetDimension(currentDimensionName, currentDimensionType); + }).Start(); + if (protocolversion >= MC115Version) dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above @@ -362,13 +376,14 @@ namespace MinecraftClient.Protocol.Handlers handler.OnTextReceived(message, true); break; case PacketTypesIn.Respawn: + string dimensionNameInRespawn = null; + Dictionary dimensionTypeInRespawn = null; if (protocolversion >= MC116Version) { - // TODO handle dimensions for 1.16+, needed for terrain handling if (protocolversion >= MC1162Version) - dataTypes.ReadNextNbt(packetData); + dimensionTypeInRespawn = dataTypes.ReadNextNbt(packetData); else - dataTypes.ReadNextString(packetData); + dataTypes.ReadNextString(packetData); this.currentDimension = 0; } else @@ -377,7 +392,14 @@ namespace MinecraftClient.Protocol.Handlers this.currentDimension = dataTypes.ReadNextInt(packetData); } if (protocolversion >= MC116Version) - dataTypes.ReadNextString(packetData); // World Name - 1.16 and above + dimensionNameInRespawn = dataTypes.ReadNextString(packetData); // World Name - 1.16 and above + + if (protocolversion >= MC1162Version) + new Task(() => + { + handler.GetWorld().SetDimension(dimensionNameInRespawn, dimensionTypeInRespawn); + }).Start(); + if (protocolversion < MC114Version) dataTypes.ReadNextByte(packetData); // Difficulty - 1.13 and below if (protocolversion >= MC115Version) @@ -435,19 +457,26 @@ namespace MinecraftClient.Protocol.Handlers int chunkZ = dataTypes.ReadNextInt(packetData); if (protocolversion >= MC117Version) { - ulong[] verticalStripBitmask = dataTypes.ReadNextULongArray(packetData); // Bit Mask Length and Primary Bit Mask + ulong[] verticalStripBitmask = null; + + if (protocolversion == MC117Version || protocolversion == MC1171Version) + verticalStripBitmask = dataTypes.ReadNextULongArray(packetData); // Bit Mask Length and Primary Bit Mask + dataTypes.ReadNextNbt(packetData); // Heightmaps - int biomesLength = dataTypes.ReadNextVarInt(packetData); // Biomes length - for (int i = 0; i < biomesLength; i++) + if (protocolversion == MC117Version || protocolversion == MC1171Version) { - dataTypes.SkipNextVarInt(packetData); // Biomes + int biomesLength = dataTypes.ReadNextVarInt(packetData); // Biomes length + for (int i = 0; i < biomesLength; i++) + { + dataTypes.SkipNextVarInt(packetData); // Biomes + } } int dataSize = dataTypes.ReadNextVarInt(packetData); // Size new Task(() => { - pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, currentDimension, packetData); + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, packetData); }).Start(); } else @@ -600,10 +629,10 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion >= MC1162Version) { long chunkSection = dataTypes.ReadNextLong(packetData); - int sectionX = (int)(chunkSection >> 42); - int sectionY = (int)((chunkSection << 44) >> 44); - int sectionZ = (int)((chunkSection << 22) >> 42); - dataTypes.ReadNextBool(packetData); // Useless boolean + int sectionX = (int)((chunkSection >> 42) & 0x3FFFFF); + int sectionZ = (int)((chunkSection >> 20) & 0x3FFFFF); + int sectionY = (int)((chunkSection) & 0xFFFFF); + dataTypes.ReadNextBool(packetData); // Useless boolean (Related to light update) int blocksSize = dataTypes.ReadNextVarInt(packetData); for (int i = 0; i < blocksSize; i++) { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index 0a932bca..f2a22b9f 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -27,123 +27,150 @@ namespace MinecraftClient.Protocol.Handlers this.handler = handler; } + /// + /// Reading the "Block states" field: consists of 4096 entries, representing all the blocks in the chunk section. + /// + /// Blocks will store in this chunk + /// Cache for reading data + private Chunk ReadBlockStatesField(ref Chunk chunk, Queue cache) + { + // read Block states (Type: Paletted Container) + byte bitsPerEntry = dataTypes.ReadNextByte(cache); + + // 1.18(1.18.1) add a pattle named "Single valued" to replace the vertical strip bitmask in the old + if (bitsPerEntry == 0 && protocolversion >= Protocol18Handler.MC1181Version) + { + // Palettes: Single valued - 1.18(1.18.1) and above + ushort value = (ushort)dataTypes.ReadNextVarInt(cache); + + dataTypes.SkipNextVarInt(cache); // Data Array Length will be zero + + // Empty chunks will not be stored + if (new Block(value).Type == Material.Air) + return null; + + 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(value); + } + } + } + } + else + { + // Palettes: Indirect or Direct + bool usePalette = (bitsPerEntry <= 8); + + // Indirect Mode: For block states with bits per entry <= 4, 4 bits are used to represent a block. + if (bitsPerEntry < 4) bitsPerEntry = 4; + + // Direct Mode: Bit mask covering bitsPerEntry bits + // EG, if bitsPerEntry = 5, valueMask = 00011111 in binary + uint valueMask = (uint)((1 << bitsPerEntry) - 1); + + int paletteLength = 0; // Assume zero when length is absent + if (usePalette) paletteLength = dataTypes.ReadNextVarInt(cache); + + int[] palette = new int[paletteLength]; + for (int i = 0; i < paletteLength; i++) + palette[i] = dataTypes.ReadNextVarInt(cache); + + // Block IDs are packed in the array of 64-bits integers + ulong[] dataArray = dataTypes.ReadNextULongArray(cache); + + int longIndex = 0; + int startOffset = 0 - bitsPerEntry; + for (int blockY = 0; blockY < Chunk.SizeY; blockY++) + { + for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) + { + for (int blockX = 0; blockX < 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 + ushort blockId; + + // Calculate location of next block ID inside the array of Longs + startOffset += bitsPerEntry; + + if ((startOffset + bitsPerEntry) > 64) + { + // 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++; + } + + // Extract Block ID + blockId = (ushort)((dataArray[longIndex] >> startOffset) & valueMask); + + // Map small IDs to actual larger block IDs + if (usePalette) + { + 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, + bitsPerEntry, + blockNumber)); + } + + blockId = (ushort)palette[blockId]; + } + + // We have our block, save the block into the chunk + chunk[blockX, blockY, blockZ] = new Block(blockId); + } + } + } + } + + return chunk; + } + /// /// Process chunk column data from the server and (un)load the chunk from the Minecraft world - 1.17 and above /// /// Chunk X location /// Chunk Z location - /// Chunk mask for reading data, store in bitset - /// Current dimension type (0 = overworld) + /// Chunk mask for reading data, store in bitset, used in 1.17 and 1.17.1 /// Cache for reading chunk data - public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[] chunkMasks, int currentDimension, Queue cache) + public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[] verticalStripBitmask, Queue cache) { - int chunkColumnSize = chunkMasks.Length * 64; + var world = handler.GetWorld(); + while (world.GetDimension() == null) + ; // Dimension parsing unfinished + + int chunkColumnSize = (world.GetDimension().height + 15) / 16; + if (protocolversion >= Protocol18Handler.MC117Version) { // 1.17 and above chunk format // Unloading chunks is handled by a separate packet for (int chunkY = 0; chunkY < chunkColumnSize; chunkY++) { - if ((chunkMasks[chunkY / 64] & (1UL << (chunkY % 64))) != 0) + // 1.18 and above always contains all chunk section in data + // 1.17 and 1.17.1 need vertical strip bitmask to know if the chunk section is included + if ((protocolversion >= Protocol18Handler.MC1181Version) || + (((protocolversion == Protocol18Handler.MC117Version) || + (protocolversion == Protocol18Handler.MC1171Version)) && + ((verticalStripBitmask[chunkY / 64] & (1UL << (chunkY % 64))) != 0))) { // Non-air block count inside chunk section, for lighting purposes int blockCnt = dataTypes.ReadNextShort(cache); - // read Block states (Type: Paletted Container) + // Read Block states (Type: Paletted Container) Chunk chunk = new Chunk(); - - byte bitsPerEntry = dataTypes.ReadNextByte(cache); - if (bitsPerEntry == 0 && protocolversion >= Protocol18Handler.MC1181Version) - { - // Palettes: Single valued - 1.xx and above - ushort value = (ushort)dataTypes.ReadNextVarInt(cache); - - dataTypes.SkipNextVarInt(cache); // Data Array Length will be zero - - 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(value); - } - } - } - } - else - { - // Palettes: Indirect or Direct - bool usePalette = (bitsPerEntry <= 8); - - // Indirect Mode: For block states with bits per entry <= 4, 4 bits are used to represent a block. - if (bitsPerEntry < 4) bitsPerEntry = 4; - - // Direct Mode: Bit mask covering bitsPerBlock bits - // EG, if bitsPerBlock = 5, valueMask = 00011111 in binary - uint valueMask = (uint)((1 << bitsPerEntry) - 1); - - int paletteLength = 0; // Assume zero when length is absent - if (usePalette) paletteLength = dataTypes.ReadNextVarInt(cache); - - int[] palette = new int[paletteLength]; - for (int i = 0; i < paletteLength; i++) - palette[i] = dataTypes.ReadNextVarInt(cache); - - // Block IDs are packed in the array of 64-bits integers - ulong[] dataArray = dataTypes.ReadNextULongArray(cache); - - int longIndex = 0; - int startOffset = 0 - bitsPerEntry; - for (int blockY = 0; blockY < Chunk.SizeY; blockY++) - { - for (int blockZ = 0; blockZ < Chunk.SizeZ; blockZ++) - { - for (int blockX = 0; blockX < 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 - ushort blockId; - - // Calculate location of next block ID inside the array of Longs - startOffset += bitsPerEntry; - - if ((startOffset + bitsPerEntry) > 64) - { - // 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++; - } - - // Extract Block ID - blockId = (ushort)((dataArray[longIndex] >> startOffset) & valueMask); - - // Map small IDs to actual larger block IDs - if (usePalette) - { - 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, - bitsPerEntry, - blockNumber)); - } - - blockId = (ushort)palette[blockId]; - } - - // We have our block, save the block into the chunk - chunk[blockX, blockY, blockZ] = new Block(blockId); - } - } - } - } + ReadBlockStatesField(ref chunk, cache); //We have our chunk, save the chunk into the world handler.InvokeOnMainThread(() => @@ -153,11 +180,11 @@ namespace MinecraftClient.Protocol.Handlers handler.GetWorld()[chunkX, chunkZ][chunkY] = chunk; }); + // Skip Read Biomes (Type: Paletted Container) - 1.18(1.18.1) and above if (protocolversion >= Protocol18Handler.MC1181Version) { - // skip read Biomes (Type: Paletted Container) byte bitsPerEntryBiome = dataTypes.ReadNextByte(cache); // Bits Per Entry - if (bitsPerEntryBiome == 0 && protocolversion >= Protocol18Handler.MC1181Version) + if (bitsPerEntryBiome == 0) { dataTypes.SkipNextVarInt(cache); // Value dataTypes.SkipNextVarInt(cache); // Data Array Length From 735d1824688dd101d20efd3b3fc50da311730772 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sun, 24 Jul 2022 21:48:09 +0800 Subject: [PATCH 05/15] Rename variables --- MinecraftClient/Mapping/Dimension.cs | 50 +++++++++---------- MinecraftClient/Mapping/Location.cs | 2 +- .../Protocol/Handlers/Protocol18.cs | 2 +- 3 files changed, 27 insertions(+), 27 deletions(-) diff --git a/MinecraftClient/Mapping/Dimension.cs b/MinecraftClient/Mapping/Dimension.cs index 0ca08e6e..ea512640 100644 --- a/MinecraftClient/Mapping/Dimension.cs +++ b/MinecraftClient/Mapping/Dimension.cs @@ -20,7 +20,7 @@ namespace MinecraftClient.Mapping /// /// Whether piglins shake and transform to zombified piglins. /// - public readonly bool piglin_safe; + public readonly bool piglinSafe; /// /// When false, compasses spin randomly. When true, nether portals can spawn zombified piglins. @@ -30,7 +30,7 @@ namespace MinecraftClient.Mapping /// /// How much light the dimension has. /// - public readonly float ambient_light; + public readonly float ambientLight; /// @@ -38,7 +38,7 @@ namespace MinecraftClient.Mapping /// Value: -1: not set /// Value: [0, 24000]: time of the day /// - public readonly long fixed_time = -1; + public readonly long fixedTime = -1; /// /// A resource location defining what block tag to use for infiniburn. @@ -49,17 +49,17 @@ namespace MinecraftClient.Mapping /// /// Whether players can charge and use respawn anchors. /// - public readonly bool respawn_anchor_works; + public readonly bool respawnAnchorWorks; /// /// Whether the dimension has skylight access or not. /// - public readonly bool has_skylight; + public readonly bool hasSkylight; /// /// Whether players can use a bed to sleep. /// - public readonly bool bed_works; + public readonly bool bedWorks; /// /// unknown @@ -70,17 +70,17 @@ namespace MinecraftClient.Mapping /// /// Whether players with the Bad Omen effect can cause a raid. /// - public readonly bool has_raids; + public readonly bool hasRaids; /// /// The minimum Y level. /// - public readonly int min_y = 0; + public readonly int minY = 0; /// /// The minimum Y level. /// - public readonly int max_y = 256; + public readonly int maxY = 256; /// /// The maximum height. @@ -90,12 +90,12 @@ namespace MinecraftClient.Mapping /// /// The maximum height to which chorus fruits and nether portals can bring players within this dimension. /// - public readonly int logical_height; + public readonly int logicalHeight; /// /// The multiplier applied to coordinates when traveling to the dimension. /// - public readonly double coordinate_scale; + public readonly double coordinateScale; /// /// Whether the dimensions behaves like the nether (water evaporates and sponges dry) or not. Also causes lava to spread thinner. @@ -105,7 +105,7 @@ namespace MinecraftClient.Mapping /// /// Whether the dimension has a bedrock ceiling or not. When true, causes lava to spread faster. /// - public readonly bool has_ceiling; + public readonly bool hasCeiling; /// @@ -124,45 +124,45 @@ namespace MinecraftClient.Mapping this.Name = name; if (nbt.ContainsKey("piglin_safe")) - this.piglin_safe = 1 == (byte)nbt["piglin_safe"]; + this.piglinSafe = 1 == (byte)nbt["piglin_safe"]; if (nbt.ContainsKey("natural")) this.natural = 1 == (byte)nbt["natural"]; if (nbt.ContainsKey("ambient_light")) - this.ambient_light = (float)nbt["ambient_light"]; + this.ambientLight = (float)nbt["ambient_light"]; if (nbt.ContainsKey("fixed_time")) - this.fixed_time = (long)nbt["fixed_time"]; + this.fixedTime = (long)nbt["fixed_time"]; if (nbt.ContainsKey("infiniburn")) this.infiniburn = (string)nbt["infiniburn"]; if (nbt.ContainsKey("respawn_anchor_works")) - this.respawn_anchor_works = 1 == (byte)nbt["respawn_anchor_works"]; + this.respawnAnchorWorks = 1 == (byte)nbt["respawn_anchor_works"]; if (nbt.ContainsKey("has_skylight")) - this.has_skylight = 1 == (byte)nbt["has_skylight"]; + this.hasSkylight = 1 == (byte)nbt["has_skylight"]; if (nbt.ContainsKey("bed_works")) - this.bed_works = 1 == (byte)nbt["bed_works"]; + this.bedWorks = 1 == (byte)nbt["bed_works"]; if (nbt.ContainsKey("effects")) this.effects = (string)nbt["effects"]; if (nbt.ContainsKey("has_raids")) - this.has_raids = 1 == (byte)nbt["has_raids"]; + this.hasRaids = 1 == (byte)nbt["has_raids"]; if (nbt.ContainsKey("min_y")) - this.min_y = (int)nbt["min_y"]; + this.minY = (int)nbt["min_y"]; if (nbt.ContainsKey("height")) this.height = (int)nbt["height"]; if (nbt.ContainsKey("min_y") && nbt.ContainsKey("height")) - this.max_y = this.min_y + this.height; + this.maxY = this.minY + this.height; if (nbt.ContainsKey("logical_height")) - this.logical_height = (int)nbt["logical_height"]; + this.logicalHeight = (int)nbt["logical_height"]; if (nbt.ContainsKey("coordinate_scale")) { var coordinate_scale_obj = nbt["coordinate_scale"]; if (coordinate_scale_obj.GetType() == typeof(float)) - this.coordinate_scale = (float)coordinate_scale_obj; + this.coordinateScale = (float)coordinate_scale_obj; else - this.coordinate_scale = (double)coordinate_scale_obj; + this.coordinateScale = (double)coordinate_scale_obj; } if (nbt.ContainsKey("ultrawarm")) this.ultrawarm = 1 == (byte)nbt["ultrawarm"]; if (nbt.ContainsKey("has_ceiling")) - this.has_ceiling = 1 == (byte)nbt["has_ceiling"]; + this.hasCeiling = 1 == (byte)nbt["has_ceiling"]; } } diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index ad02d75a..3143fd99 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -87,7 +87,7 @@ namespace MinecraftClient.Mapping if (world.GetDimension() == null) return (int)Math.Floor(Y / Chunk.SizeY); // old version, always start at zero else - return (int)Math.Floor((Y - world.GetDimension().min_y) / Chunk.SizeY); + return (int)Math.Floor((Y - world.GetDimension().minY) / Chunk.SizeY); } } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index a55a214c..28ee0d77 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -392,7 +392,7 @@ namespace MinecraftClient.Protocol.Handlers this.currentDimension = dataTypes.ReadNextInt(packetData); } if (protocolversion >= MC116Version) - dimensionNameInRespawn = dataTypes.ReadNextString(packetData); // World Name - 1.16 and above + dimensionNameInRespawn = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above if (protocolversion >= MC1162Version) new Task(() => From 34299895276b532da87309fc860de276b0d9b9a1 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sun, 24 Jul 2022 22:03:02 +0800 Subject: [PATCH 06/15] change .NET version to 4.0 --- MinecraftClient/Mapping/Dimension.cs | 8 ++++---- MinecraftClient/Mapping/Location.cs | 2 +- MinecraftClient/MinecraftClient.csproj | 10 +++------- MinecraftClient/Protocol/Handlers/Protocol18.cs | 3 +-- MinecraftClient/app.config | 3 --- 5 files changed, 9 insertions(+), 17 deletions(-) delete mode 100644 MinecraftClient/app.config diff --git a/MinecraftClient/Mapping/Dimension.cs b/MinecraftClient/Mapping/Dimension.cs index ea512640..987209ff 100644 --- a/MinecraftClient/Mapping/Dimension.cs +++ b/MinecraftClient/Mapping/Dimension.cs @@ -153,11 +153,11 @@ namespace MinecraftClient.Mapping this.logicalHeight = (int)nbt["logical_height"]; if (nbt.ContainsKey("coordinate_scale")) { - var coordinate_scale_obj = nbt["coordinate_scale"]; - if (coordinate_scale_obj.GetType() == typeof(float)) - this.coordinateScale = (float)coordinate_scale_obj; + var coordinateScaleObj = nbt["coordinate_scale"]; + if (coordinateScaleObj.GetType() == typeof(float)) + this.coordinateScale = (float)coordinateScaleObj; else - this.coordinateScale = (double)coordinate_scale_obj; + this.coordinateScale = (double)coordinateScaleObj; } if (nbt.ContainsKey("ultrawarm")) this.ultrawarm = 1 == (byte)nbt["ultrawarm"]; diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index 3143fd99..e8f2178d 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -85,7 +85,7 @@ namespace MinecraftClient.Mapping get { if (world.GetDimension() == null) - return (int)Math.Floor(Y / Chunk.SizeY); // old version, always start at zero + return (int)Math.Floor(Y / Chunk.SizeY); // below 1.16.2, Y coordinate always start from zero else return (int)Math.Floor((Y - world.GetDimension().minY) / Chunk.SizeY); } diff --git a/MinecraftClient/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index 86351c15..46521de9 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -1,5 +1,5 @@  - + Debug x86 @@ -10,9 +10,8 @@ Properties MinecraftClient MinecraftClient - v4.8 - - + v4.0 + Client 512 publish\ true @@ -39,7 +38,6 @@ DEBUG;TRACE prompt 4 - false x86 @@ -49,7 +47,6 @@ TRACE prompt 4 - false @@ -422,7 +419,6 @@ - diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 28ee0d77..a0b9766b 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -302,7 +302,6 @@ namespace MinecraftClient.Protocol.Handlers for (int i = 0; i < worldCount; i++) dataTypes.ReadNextString(packetData); // World Names - 1.16 and above dataTypes.ReadNextNbt(packetData); // Dimension Codec - 1.16 and above - } string currentDimensionName = null; @@ -383,7 +382,7 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion >= MC1162Version) dimensionTypeInRespawn = dataTypes.ReadNextNbt(packetData); else - dataTypes.ReadNextString(packetData); + dataTypes.ReadNextString(packetData); this.currentDimension = 0; } else diff --git a/MinecraftClient/app.config b/MinecraftClient/app.config deleted file mode 100644 index 3e0e37cf..00000000 --- a/MinecraftClient/app.config +++ /dev/null @@ -1,3 +0,0 @@ - - - From 59ed18bb407c182a0cec04b5e562f858511126c3 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Sun, 24 Jul 2022 22:21:15 +0800 Subject: [PATCH 07/15] Fixed incorrect handling in 1.18(1.18.1) and 1.18.2 --- MinecraftClient/ChatBot.cs | 2 +- MinecraftClient/McClient.cs | 2 +- MinecraftClient/Protocol/Handlers/DataTypes.cs | 5 ++++- MinecraftClient/Protocol/Handlers/Protocol18.cs | 11 ++++++++--- MinecraftClient/Protocol/IMinecraftComHandler.cs | 2 +- 5 files changed, 15 insertions(+), 7 deletions(-) diff --git a/MinecraftClient/ChatBot.cs b/MinecraftClient/ChatBot.cs index 3b37d98b..5e354399 100644 --- a/MinecraftClient/ChatBot.cs +++ b/MinecraftClient/ChatBot.cs @@ -327,7 +327,7 @@ namespace MinecraftClient /// 0 to create/update an item. 1 to remove an item. /// The name of the objective the score belongs to /// The score to be displayed next to the entry. Only sent when Action does not equal 1. - public virtual void OnUpdateScore(string entityname, byte action, string objectivename, int value) { } + public virtual void OnUpdateScore(string entityname, int action, string objectivename, int value) { } /// /// Called when an inventory/container was updated by server diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 9d0fbf7c..d608cc5d 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -2490,7 +2490,7 @@ namespace MinecraftClient /// 0 to create/update an item. 1 to remove an item. /// The name of the objective the score belongs to /// he score to be displayed next to the entry. Only sent when Action does not equal 1. - public void OnUpdateScore(string entityname, byte action, string objectivename, int value) + public void OnUpdateScore(string entityname, int action, string objectivename, int value) { DispatchBotEvent(bot => bot.OnUpdateScore(entityname, action, objectivename, value)); } diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index 3bcacf7b..b8f894b6 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -410,7 +410,10 @@ namespace MinecraftClient.Protocol.Handlers if (living) { - entityPitch = ReadNextByte(cache); + if (protocolversion >= Protocol18Handler.MC1182Version) + entityYaw = ReadNextByte(cache); + else + entityPitch = ReadNextByte(cache); } else { diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index a0b9766b..abc4f2bb 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -1208,7 +1208,7 @@ namespace MinecraftClient.Protocol.Handlers break; case PacketTypesIn.UpdateScore: string entityname = dataTypes.ReadNextString(packetData); - byte action3 = dataTypes.ReadNextByte(packetData); + int action3 = protocolversion >= MC1182Version ? dataTypes.ReadNextVarInt(packetData) : dataTypes.ReadNextByte(packetData); string objectivename2 = null; int value = -1; if (action3 != 1 || protocolversion >= MC18Version) @@ -1713,7 +1713,12 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion >= MC19Version) fields.AddRange(dataTypes.GetVarInt(mainHand)); if (protocolversion >= MC117Version) - fields.Add(1); // 1.17 and above - Disable text filtering. (Always true) + { + if (protocolversion == MC117Version || protocolversion == MC1171Version) + fields.Add(1); // 1.17 and 1.17.1 - Disable text filtering. (Always true) + else + fields.Add(0); // 1.18 and above - Enable text filtering. (Always false) + } if (protocolversion >= MC1181Version) fields.Add(1); // 1.18 and above - Allow server listings SendPacket(PacketTypesOut.ClientSettings, fields); @@ -2040,7 +2045,7 @@ namespace MinecraftClient.Protocol.Handlers packet.AddRange(arrayOfSlots); } - packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); // Clicked item + packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); // Carried item (Clicked item) log.Info("Packet data: " + dataTypes.ByteArrayToString(packet.ToArray())); diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 2b63d5e7..4ca8faf9 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -374,7 +374,7 @@ namespace MinecraftClient.Protocol /// 0 to create/update an item. 1 to remove an item. /// The name of the objective the score belongs to /// he score to be displayed next to the entry. Only sent when Action does not equal 1. - void OnUpdateScore(string entityname, byte action, string objectivename, int value); + void OnUpdateScore(string entityname, int action, string objectivename, int value); /// /// Called when tradeList is received from server From 86dfd60d07b6612fd30b2784f5acae1a80b7d91e Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 01:13:41 +0800 Subject: [PATCH 08/15] =?UTF-8?q?Bugs=20fix=20for=20=E2=80=9CClickWindow?= =?UTF-8?q?=E2=80=9C=20packet?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- MinecraftClient/McClient.cs | 44 +++++++++++++++++-- .../Protocol/Handlers/Protocol16.cs | 2 +- .../Protocol/Handlers/Protocol18.cs | 43 +++++++++--------- MinecraftClient/Protocol/IMinecraftCom.cs | 6 ++- .../Protocol/IMinecraftComHandler.cs | 3 +- 5 files changed, 67 insertions(+), 31 deletions(-) diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index d608cc5d..2f517021 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1243,8 +1243,7 @@ namespace MinecraftClient if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId)) item = inventories[windowId].Items[slotId]; - // Inventory update must be after sending packet - bool result = handler.SendWindowAction(windowId, slotId, action, item, inventories[windowId].Items, inventories[windowId].StateID); + List> changedSlots = new List>(); // List // Update our inventory base on action type var inventory = GetInventory(windowId); @@ -1295,6 +1294,8 @@ namespace MinecraftClient inventory.Items[slotId] = playerInventory.Items[-1]; playerInventory.Items.Remove(-1); } + + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } else { @@ -1307,6 +1308,8 @@ namespace MinecraftClient // Put target slot item to cursor playerInventory.Items[-1] = inventory.Items[slotId]; inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)slotId, null)); } } break; @@ -1385,6 +1388,7 @@ namespace MinecraftClient } } } + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); break; case WindowActionType.ShiftClick: if (slotId == 0) break; @@ -1412,6 +1416,7 @@ namespace MinecraftClient // If hotbar already have same item, will put on it first until every stack are full // If no more same item , will put on the first empty slot (smaller slot id) // If inventory full, item will not move + int itemCount = inventory.Items[slotId].Count; if (slotId <= upperEndSlot) { // Clicked slot is on upper side inventory, put it to hotbar @@ -1431,11 +1436,16 @@ namespace MinecraftClient // Can fit into the stack inventory.Items[_item.Key].Count += inventory.Items[slotId].Count; inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)_item.Key, inventory.Items[_item.Key])); + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } else { inventory.Items[slotId].Count -= spaceLeft; inventory.Items[_item.Key].Count = inventory.Items[_item.Key].Type.StackCount(); + + changedSlots.Add(new Tuple((short)_item.Key, inventory.Items[_item.Key])); } } } @@ -1454,6 +1464,13 @@ namespace MinecraftClient var itemTmp = inventory.Items[slotId]; inventory.Items[emptySlot] = new Item(itemTmp.Type, itemTmp.Count, itemTmp.NBT); inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)emptySlot, inventory.Items[emptySlot])); + changedSlots.Add(new Tuple((short)slotId, null)); + } + else if (inventory.Items[slotId].Count != itemCount) + { + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } } } @@ -1477,11 +1494,16 @@ namespace MinecraftClient // Can fit into the stack inventory.Items[_item.Key].Count += inventory.Items[slotId].Count; inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)_item.Key, inventory.Items[_item.Key])); + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } else { inventory.Items[slotId].Count -= spaceLeft; inventory.Items[_item.Key].Count = inventory.Items[_item.Key].Type.StackCount(); + + changedSlots.Add(new Tuple((short)_item.Key, inventory.Items[_item.Key])); } } } @@ -1501,6 +1523,13 @@ namespace MinecraftClient var itemTmp = inventory.Items[slotId]; inventory.Items[emptySlot] = new Item(itemTmp.Type, itemTmp.Count, itemTmp.NBT); inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)emptySlot, inventory.Items[emptySlot])); + changedSlots.Add(new Tuple((short)slotId, null)); + } + else if (inventory.Items[slotId].Count != itemCount) + { + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } } } @@ -1512,14 +1541,18 @@ namespace MinecraftClient if (inventory.Items[slotId].Count <= 0) inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); break; case WindowActionType.DropItemStack: inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)slotId, null)); break; } } - return result; + return handler.SendWindowAction(windowId, slotId, action, item, changedSlots, inventories[windowId].StateID); } /// @@ -2046,8 +2079,11 @@ namespace MinecraftClient /// Window ID /// Slot ID /// Item (may be null for empty slot) - public void OnSetSlot(byte inventoryID, short slotID, Item item) + public void OnSetSlot(byte inventoryID, short slotID, Item item, int stateId) { + if (inventories.ContainsKey(inventoryID)) + inventories[inventoryID].StateID = stateId; + // Handle inventoryID -2 - Add item to player inventory without animation if (inventoryID == 254) inventoryID = 0; diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index c7e0b9ef..0f30bfce 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -720,7 +720,7 @@ namespace MinecraftClient.Protocol.Handlers return false; //Currently not implemented } - public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, Dictionary Items, int stateId) + public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List> changedSlots, int stateId) { return false; //Currently not implemented } diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index abc4f2bb..cd8f831e 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -917,7 +917,7 @@ namespace MinecraftClient.Protocol.Handlers short slotID = dataTypes.ReadNextShort(packetData); Item item = dataTypes.ReadNextItemSlot(packetData, itemPalette); - handler.OnSetSlot(windowID, slotID, item); + handler.OnSetSlot(windowID, slotID, item, stateId); } break; case PacketTypesIn.WindowConfirmation: @@ -1967,7 +1967,7 @@ namespace MinecraftClient.Protocol.Handlers catch (ObjectDisposedException) { return false; } } - public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, Dictionary items, int stateId) + public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List> changedSlots, int stateId) { try { @@ -2004,51 +2004,48 @@ namespace MinecraftClient.Protocol.Handlers List packet = new List(); - log.Info("Window id: " + windowId + " - State id: " + stateId + " - Slot id: " + slotId + " - Mode: " + mode); - log.Info("Bytes > " + (byte)windowId + " - State id: " + dataTypes.ByteArrayToString(dataTypes.GetVarInt(stateId)) + " - Slot id: " + dataTypes.ByteArrayToString(dataTypes.GetVarInt(slotId)) + " - Mode: " + dataTypes.ByteArrayToString(dataTypes.GetVarInt(mode))); - - packet.Add((byte)windowId); + packet.Add((byte)windowId); // Window ID // 1.18+ if (protocolversion >= MC1181Version) { - packet.AddRange(dataTypes.GetVarInt(stateId)); - packet.AddRange(dataTypes.GetShort((short)slotId)); + packet.AddRange(dataTypes.GetVarInt(stateId)); // State ID + packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID } // 1.17.1 else if (protocolversion == MC1171Version) { - packet.AddRange(dataTypes.GetShort((short)slotId)); - packet.AddRange(dataTypes.GetVarInt(stateId)); + packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID + packet.AddRange(dataTypes.GetVarInt(stateId)); // State ID } // Older else { - packet.AddRange(dataTypes.GetShort((short)slotId)); + packet.AddRange(dataTypes.GetShort((short)slotId)); // Slot ID } - packet.Add(button); - if (protocolversion < MC117Version) packet.AddRange(dataTypes.GetShort(actionNumber)); + packet.Add(button); // Button + + if (protocolversion < MC117Version) + packet.AddRange(dataTypes.GetShort(actionNumber)); + if (protocolversion >= MC19Version) - packet.AddRange(dataTypes.GetVarInt(mode)); + packet.AddRange(dataTypes.GetVarInt(mode)); // Mode else packet.Add(mode); // 1.17+ if (protocolversion >= MC117Version) { - byte[] arrayOfSlots = dataTypes.GetSlotsArray(items, itemPalette); - - log.Info("Length: " + dataTypes.ByteArrayToString(dataTypes.GetVarInt(arrayOfSlots.Length)) + " (" + arrayOfSlots.Length + ")"); - log.Info("Array: " + dataTypes.ByteArrayToString(arrayOfSlots)); - - packet.AddRange(dataTypes.GetVarInt(arrayOfSlots.Length)); - packet.AddRange(arrayOfSlots); + packet.AddRange(dataTypes.GetVarInt(changedSlots.Count)); // Length of the array + foreach (var slot in changedSlots) + { + packet.AddRange(dataTypes.GetShort(slot.Item1)); // slot ID + packet.AddRange(dataTypes.GetItemSlot(slot.Item2, itemPalette)); // slot Data + } } packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); // Carried item (Clicked item) - log.Info("Packet data: " + dataTypes.ByteArrayToString(packet.ToArray())); - SendPacket(PacketTypesOut.ClickWindow, packet); return true; } diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 97aaca35..487e8190 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -162,10 +162,12 @@ namespace MinecraftClient.Protocol /// /// Id of the window being clicked /// Id of the clicked slot - /// Action to perform + /// Action to perform /// Item in the clicked slot + /// Slots that have been changed in this event: List + /// Inventory's stateId /// True if packet was successfully sent - bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, Dictionary Items, int stateId); + bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List> changedSlots, int stateId); /// /// Request Creative Mode item creation into regular/survival Player Inventory diff --git a/MinecraftClient/Protocol/IMinecraftComHandler.cs b/MinecraftClient/Protocol/IMinecraftComHandler.cs index 4ca8faf9..3da38c94 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -272,7 +272,8 @@ namespace MinecraftClient.Protocol /// Window ID /// Slot ID /// Item (may be null for empty slot) - void OnSetSlot(byte inventoryID, short slotID, Item item); + /// State ID + void OnSetSlot(byte inventoryID, short slotID, Item item, int stateId); /// /// Called when player health or hunger changed. From 357820e1c2532ee0dc9ce9c94f874177a18caa2a Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 03:19:24 +0800 Subject: [PATCH 09/15] Fix bugs in gravity handle --- MinecraftClient/Commands/Move.cs | 2 +- MinecraftClient/Mapping/ChunkColumn.cs | 2 ++ MinecraftClient/Mapping/Movement.cs | 7 +++++- MinecraftClient/Mapping/World.cs | 7 ++++++ MinecraftClient/McClient.cs | 22 +++++++++++++++++++ .../Protocol/Handlers/Protocol18.cs | 16 +++++++++++--- .../Protocol/Handlers/Protocol18Terrain.cs | 5 +++-- 7 files changed, 54 insertions(+), 7 deletions(-) diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 56fde8e3..7ccd3ed5 100644 --- a/MinecraftClient/Commands/Move.cs +++ b/MinecraftClient/Commands/Move.cs @@ -76,7 +76,7 @@ namespace MinecraftClient.Commands int z = int.Parse(args[2]); Location goal = new Location(x, y, z); - if (handler.GetWorld().GetChunkColumn(goal) == null) + if (handler.GetWorld().GetChunkColumn(goal) == null || handler.GetWorld().GetChunkColumn(goal).FullyLoaded == false) return Translations.Get("cmd.move.chunk_not_loaded"); else if (handler.MoveTo(goal, allowUnsafe: takeRisk)) return Translations.Get("cmd.move.walk", goal); diff --git a/MinecraftClient/Mapping/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs index a7e704ce..5cbeda73 100644 --- a/MinecraftClient/Mapping/ChunkColumn.cs +++ b/MinecraftClient/Mapping/ChunkColumn.cs @@ -13,6 +13,8 @@ namespace MinecraftClient.Mapping { public int ColumnSize; + public bool FullyLoaded = false; + /// /// Blocks contained into the chunk /// diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs index fc66ca7a..ff2e4089 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -31,9 +31,11 @@ namespace MinecraftClient.Mapping belowFoots = location; belowFoots.Y = Math.Truncate(location.Y); } + //Console.WriteLine("IsOnGround = " + IsOnGround(world, location)); if (!IsOnGround(world, location) && !IsSwimming(world, location)) { - while (!IsOnGround(world, belowFoots) && belowFoots.Y >= 1) + while (!IsOnGround(world, belowFoots) && + belowFoots.Y >= 1 + (world.GetDimension() == null ? 0 : world.GetDimension().minY)) belowFoots = Move(belowFoots, Direction.Down); location = Move2Steps(location, belowFoots, ref motionY, true).Dequeue(); } @@ -262,6 +264,9 @@ namespace MinecraftClient.Mapping /// True if the specified location is on the ground public static bool IsOnGround(World world, Location location) { + if (world.GetChunkColumn(location) == null || world.GetChunkColumn(location).FullyLoaded == false) + return true; // avoid moving downward in a not loaded chunk + return world.GetBlock(Move(location, Direction.Down)).Type.IsSolid() && (location.Y <= Math.Truncate(location.Y) + 0.0001); } diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs index b324007a..c753708b 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -26,6 +26,13 @@ namespace MinecraftClient.Mapping /// private readonly ReaderWriterLockSlim chunksLock = new ReaderWriterLockSlim(); + /// + /// Chunk data parsing progress + /// + public uint chunkCnt = 0; + public uint chunkLoadNotCompleted = 0; + + /// /// Read, set or unload the specified chunk column /// diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 2f517021..e3a9cab0 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -59,6 +59,8 @@ namespace MinecraftClient private float playerYaw; private float playerPitch; private double motionY; + private int chunkLoadingStateTicks = 30; // Setting it to zero to disable chunk loading statu log + private double lastChunkLoadedRatio = 0; private string host; private int port; @@ -385,6 +387,26 @@ namespace MinecraftClient taskToRun(); } } + + if (terrainAndMovementsEnabled) + { + if (chunkLoadingStateTicks <= 0) + { + chunkLoadingStateTicks = 50; + if (world.chunkCnt != 0) + { + double chunkLoadedRatio = (world.chunkCnt - world.chunkLoadNotCompleted) / (double)world.chunkCnt; + if (chunkLoadedRatio != lastChunkLoadedRatio) + { + Log.Info(string.Format("Chunk loading: {0:P} {1}/{2}", + chunkLoadedRatio, world.chunkCnt - world.chunkLoadNotCompleted, world.chunkCnt)); + lastChunkLoadedRatio = chunkLoadedRatio; + } + } + } + else + chunkLoadingStateTicks--; + } } #region Connection Lost and Disconnect from Server diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index cd8f831e..687cd9cd 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -475,7 +475,10 @@ namespace MinecraftClient.Protocol.Handlers int dataSize = dataTypes.ReadNextVarInt(packetData); // Size new Task(() => { + handler.GetWorld().chunkCnt++; + handler.GetWorld().chunkLoadNotCompleted++; pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, packetData); + handler.GetWorld().chunkLoadNotCompleted--; }).Start(); } else @@ -628,9 +631,12 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion >= MC1162Version) { long chunkSection = dataTypes.ReadNextLong(packetData); - int sectionX = (int)((chunkSection >> 42) & 0x3FFFFF); - int sectionZ = (int)((chunkSection >> 20) & 0x3FFFFF); - int sectionY = (int)((chunkSection) & 0xFFFFF); + int sectionX = (int)(chunkSection >> 42); + int sectionY = (int)((chunkSection << 44) >> 44); + int sectionZ = (int)((chunkSection << 22) >> 42); + //int sectionX = (int)((chunkSection >> 42) & 0x3FFFFF); + //int sectionZ = (int)((chunkSection >> 20) & 0x3FFFFF); + //int sectionY = (int)((chunkSection) & 0xFFFFF); dataTypes.ReadNextBool(packetData); // Useless boolean (Related to light update) int blocksSize = dataTypes.ReadNextVarInt(packetData); for (int i = 0; i < blocksSize; i++) @@ -750,6 +756,10 @@ namespace MinecraftClient.Protocol.Handlers { int chunkX = dataTypes.ReadNextInt(packetData); int chunkZ = dataTypes.ReadNextInt(packetData); + + if (handler.GetWorld()[chunkX, chunkZ] != null) + handler.GetWorld().chunkCnt--; + handler.GetWorld()[chunkX, chunkZ] = null; } break; diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index f2a22b9f..6416c303 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -147,8 +147,8 @@ namespace MinecraftClient.Protocol.Handlers public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[] verticalStripBitmask, Queue cache) { var world = handler.GetWorld(); - while (world.GetDimension() == null) - ; // Dimension parsing unfinished + if (world.GetDimension() == null) + return; int chunkColumnSize = (world.GetDimension().height + 15) / 16; @@ -207,6 +207,7 @@ namespace MinecraftClient.Protocol.Handlers // 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) } + handler.GetWorld()[chunkX, chunkZ].FullyLoaded = true; } /// From ff014d29127d959be7a5d352ffd3c249055a4f80 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 03:50:31 +0800 Subject: [PATCH 10/15] Delete unnecessary comments --- MinecraftClient/Mapping/World.cs | 1 - MinecraftClient/Protocol/Handlers/Protocol18.cs | 3 --- 2 files changed, 4 deletions(-) diff --git a/MinecraftClient/Mapping/World.cs b/MinecraftClient/Mapping/World.cs index c753708b..39b5f0c4 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -32,7 +32,6 @@ namespace MinecraftClient.Mapping public uint chunkCnt = 0; public uint chunkLoadNotCompleted = 0; - /// /// Read, set or unload the specified chunk column /// diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 687cd9cd..dc9e4789 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -634,9 +634,6 @@ namespace MinecraftClient.Protocol.Handlers int sectionX = (int)(chunkSection >> 42); int sectionY = (int)((chunkSection << 44) >> 44); int sectionZ = (int)((chunkSection << 22) >> 42); - //int sectionX = (int)((chunkSection >> 42) & 0x3FFFFF); - //int sectionZ = (int)((chunkSection >> 20) & 0x3FFFFF); - //int sectionY = (int)((chunkSection) & 0xFFFFF); dataTypes.ReadNextBool(packetData); // Useless boolean (Related to light update) int blocksSize = dataTypes.ReadNextVarInt(packetData); for (int i = 0; i < blocksSize; i++) From b0b45a74a05b53f8ac2ee8f23f80c52ffa476c08 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 14:20:24 +0800 Subject: [PATCH 11/15] Make chunk loading status to be displayed using "/move" command --- MinecraftClient/Commands/Move.cs | 23 ++++++++++++++++++- MinecraftClient/Mapping/Movement.cs | 1 - MinecraftClient/McClient.cs | 22 ------------------ .../Protocol/Handlers/Protocol18.cs | 3 --- MinecraftClient/Resources/lang/en.ini | 1 + 5 files changed, 23 insertions(+), 27 deletions(-) diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 7ccd3ed5..4ccd033a 100644 --- a/MinecraftClient/Commands/Move.cs +++ b/MinecraftClient/Commands/Move.cs @@ -17,7 +17,14 @@ namespace MinecraftClient.Commands bool takeRisk = false; if (args.Count < 1) - return GetCmdDescTranslated(); + { + string desc = GetCmdDescTranslated(); + + if (handler.GetTerrainEnabled()) + handler.Log.Info(getChunkLoadingStatus(handler.GetWorld())); + + return desc; + } if (args.Contains("-f")) { @@ -88,5 +95,19 @@ namespace MinecraftClient.Commands } else return Translations.Get("extra.terrainandmovement_required"); } + + private string getChunkLoadingStatus(World world) + { + double chunkLoadedRatio; + if (world.chunkCnt == 0) + chunkLoadedRatio = 0; + else + chunkLoadedRatio = (world.chunkCnt - world.chunkLoadNotCompleted) / (double)world.chunkCnt; + + string status = Translations.Get("cmd.move.chunk_loading_status", + chunkLoadedRatio, world.chunkCnt - world.chunkLoadNotCompleted, world.chunkCnt); + + return status; + } } } diff --git a/MinecraftClient/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs index ff2e4089..960921b7 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -31,7 +31,6 @@ namespace MinecraftClient.Mapping belowFoots = location; belowFoots.Y = Math.Truncate(location.Y); } - //Console.WriteLine("IsOnGround = " + IsOnGround(world, location)); if (!IsOnGround(world, location) && !IsSwimming(world, location)) { while (!IsOnGround(world, belowFoots) && diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index e3a9cab0..2f517021 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -59,8 +59,6 @@ namespace MinecraftClient private float playerYaw; private float playerPitch; private double motionY; - private int chunkLoadingStateTicks = 30; // Setting it to zero to disable chunk loading statu log - private double lastChunkLoadedRatio = 0; private string host; private int port; @@ -387,26 +385,6 @@ namespace MinecraftClient taskToRun(); } } - - if (terrainAndMovementsEnabled) - { - if (chunkLoadingStateTicks <= 0) - { - chunkLoadingStateTicks = 50; - if (world.chunkCnt != 0) - { - double chunkLoadedRatio = (world.chunkCnt - world.chunkLoadNotCompleted) / (double)world.chunkCnt; - if (chunkLoadedRatio != lastChunkLoadedRatio) - { - Log.Info(string.Format("Chunk loading: {0:P} {1}/{2}", - chunkLoadedRatio, world.chunkCnt - world.chunkLoadNotCompleted, world.chunkCnt)); - lastChunkLoadedRatio = chunkLoadedRatio; - } - } - } - else - chunkLoadingStateTicks--; - } } #region Connection Lost and Disconnect from Server diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index dc9e4789..474bbfe0 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -332,10 +332,7 @@ namespace MinecraftClient.Protocol.Handlers currentDimensionName = dataTypes.ReadNextString(packetData); // Dimension Name (World Name) - 1.16 and above if (protocolversion >= MC1162Version) - new Task(() => - { handler.GetWorld().SetDimension(currentDimensionName, currentDimensionType); - }).Start(); if (protocolversion >= MC115Version) dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index e1a72f12..c3f5901e 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -319,6 +319,7 @@ cmd.move.fail=Failed to compute path to {0} cmd.move.suggestforce=Failed to compute a safe path to {0}. Try -f parameter to allow unsafe movements. cmd.move.gravity.enabled=Gravity is enabled. cmd.move.gravity.disabled=Gravity is disabled. +cmd.move.chunk_loading_status=Chunk loading status: {0:P} - {1} out of {2} load completed. cmd.move.chunk_not_loaded=The chunk where the target location resides has not yet been loaded. # Reco From f0af851df882ca0229ddc438fdded2f38f97bc8a Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 17:04:14 +0800 Subject: [PATCH 12/15] Fixed bug where "ChunkFullyLoaded" was not updated below 1.17 --- MinecraftClient/Protocol/Handlers/Protocol18.cs | 2 +- MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/Protocol/Handlers/Protocol18.cs b/MinecraftClient/Protocol/Handlers/Protocol18.cs index 474bbfe0..9b2dfade 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -2037,7 +2037,7 @@ namespace MinecraftClient.Protocol.Handlers packet.AddRange(dataTypes.GetVarInt(mode)); // Mode else packet.Add(mode); - // 1.17+ + // 1.17+ Array of changed slots if (protocolversion >= MC117Version) { packet.AddRange(dataTypes.GetVarInt(changedSlots.Count)); // Length of the array diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index 6416c303..73593da6 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -491,6 +491,7 @@ namespace MinecraftClient.Protocol.Handlers } } } + handler.GetWorld()[chunkX, chunkZ].FullyLoaded = true; } } } From cbe348555b91fadd11db0f0d60fc1e75eef1dd35 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 17:08:59 +0800 Subject: [PATCH 13/15] Fix bug: drop single item cause exception --- MinecraftClient/McClient.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 2f517021..3b9e8005 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1537,12 +1537,17 @@ namespace MinecraftClient break; case WindowActionType.DropItem: if (inventory.Items.ContainsKey(slotId)) + { inventory.Items[slotId].Count--; + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + } if (inventory.Items[slotId].Count <= 0) + { inventory.Items.Remove(slotId); + changedSlots.Add(new Tuple((short)slotId, null)); + } - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); break; case WindowActionType.DropItemStack: inventory.Items.Remove(slotId); From a18b526a417652dac42527e33db1df2c5da02b35 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 17:22:01 +0800 Subject: [PATCH 14/15] bug fix: Error handling for empty slots --- MinecraftClient/McClient.cs | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 3b9e8005..b59ee3c8 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1295,7 +1295,10 @@ namespace MinecraftClient playerInventory.Items.Remove(-1); } - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + if (inventory.Items.ContainsKey(slotId)) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + else + changedSlots.Add(new Tuple((short)slotId, null)); } else { @@ -1388,7 +1391,10 @@ namespace MinecraftClient } } } - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + if (inventory.Items.ContainsKey(slotId)) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + else + changedSlots.Add(new Tuple((short)slotId, null)); break; case WindowActionType.ShiftClick: if (slotId == 0) break; @@ -1438,7 +1444,7 @@ namespace MinecraftClient inventory.Items.Remove(slotId); changedSlots.Add(new Tuple((short)_item.Key, inventory.Items[_item.Key])); - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + changedSlots.Add(new Tuple((short)slotId, null)); } else { @@ -1496,7 +1502,7 @@ namespace MinecraftClient inventory.Items.Remove(slotId); changedSlots.Add(new Tuple((short)_item.Key, inventory.Items[_item.Key])); - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + changedSlots.Add(new Tuple((short)slotId, null)); } else { @@ -1551,7 +1557,6 @@ namespace MinecraftClient break; case WindowActionType.DropItemStack: inventory.Items.Remove(slotId); - changedSlots.Add(new Tuple((short)slotId, null)); break; } From 94fd8b118de50caa9863b8e9f73b024e1d491ff8 Mon Sep 17 00:00:00 2001 From: BruceChen Date: Mon, 25 Jul 2022 18:11:10 +0800 Subject: [PATCH 15/15] "/move X Y Z" now moves the player to the center of the block first --- MinecraftClient/Commands/Move.cs | 18 +++++++++++++++--- MinecraftClient/Mapping/Location.cs | 2 +- MinecraftClient/Resources/lang/en.ini | 2 +- 3 files changed, 17 insertions(+), 5 deletions(-) diff --git a/MinecraftClient/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 4ccd033a..8b5fecab 100644 --- a/MinecraftClient/Commands/Move.cs +++ b/MinecraftClient/Commands/Move.cs @@ -8,7 +8,7 @@ namespace MinecraftClient.Commands public class Move : Command { public override string CmdName { get { return "move"; } } - public override string CmdUsage { get { return "move [-f]"; } } + public override string CmdUsage { get { return "move [-f]"; } } public override string CmdDesc { get { return "walk or start walking. \"-f\": force unsafe movements like falling or touching fire"; } } public override string Run(McClient handler, string command, Dictionary localVars) @@ -63,6 +63,13 @@ namespace MinecraftClient.Commands case "west": direction = Direction.West; break; case "north": direction = Direction.North; break; case "south": direction = Direction.South; break; + case "center": + { + Location current = handler.GetCurrentLocation(); + Location currentCenter = new Location(Math.Floor(current.X) + 0.5, current.Y, Math.Floor(current.Z) + 0.5); + handler.MoveTo(currentCenter, allowDirectTeleport: true); + return Translations.Get("cmd.move.walk", currentCenter, current); + } case "get": return handler.GetCurrentLocation().ToString(); default: return Translations.Get("cmd.look.unknown", args[0]); } @@ -85,8 +92,13 @@ namespace MinecraftClient.Commands if (handler.GetWorld().GetChunkColumn(goal) == null || handler.GetWorld().GetChunkColumn(goal).FullyLoaded == false) return Translations.Get("cmd.move.chunk_not_loaded"); - else if (handler.MoveTo(goal, allowUnsafe: takeRisk)) - return Translations.Get("cmd.move.walk", goal); + + Location current = handler.GetCurrentLocation(); + Location currentCenter = new Location(Math.Floor(current.X) + 0.5, current.Y, Math.Floor(current.Z) + 0.5); + handler.MoveTo(currentCenter, allowDirectTeleport: true); + + if (handler.MoveTo(goal, allowUnsafe: takeRisk)) + return Translations.Get("cmd.move.walk", goal, current); else return takeRisk ? Translations.Get("cmd.move.fail", goal) : Translations.Get("cmd.move.suggestforce", goal); } catch (FormatException) { return GetCmdDescTranslated(); } diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index e8f2178d..9dab848a 100644 --- a/MinecraftClient/Mapping/Location.cs +++ b/MinecraftClient/Mapping/Location.cs @@ -307,7 +307,7 @@ namespace MinecraftClient.Mapping /// String representation of the location public override string ToString() { - return String.Format("X:{0} Y:{1} Z:{2}", X, Y, Z); + return String.Format("X:{0:0.00} Y:{1:0.00} Z:{2:0.00}", X, Y, Z); } } } diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index c3f5901e..c773e62c 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -314,7 +314,7 @@ cmd.move.enable=Enabling Terrain and Movements on next server login, respawn or cmd.move.disable=Disabling Terrain and Movements. cmd.move.moving=Moving {0} cmd.move.dir_fail=Cannot move in that direction. -cmd.move.walk=Walking to {0} +cmd.move.walk=Walking from {1} to {0} cmd.move.fail=Failed to compute path to {0} cmd.move.suggestforce=Failed to compute a safe path to {0}. Try -f parameter to allow unsafe movements. cmd.move.gravity.enabled=Gravity is enabled.