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/Commands/Move.cs b/MinecraftClient/Commands/Move.cs index 0af06344..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) @@ -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")) { @@ -56,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]); } @@ -76,8 +90,15 @@ namespace MinecraftClient.Commands int z = int.Parse(args[2]); Location goal = new Location(x, y, z); + if (handler.GetWorld().GetChunkColumn(goal) == null || handler.GetWorld().GetChunkColumn(goal).FullyLoaded == false) + return Translations.Get("cmd.move.chunk_not_loaded"); + + 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); + 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(); } @@ -86,5 +107,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/ChunkColumn.cs b/MinecraftClient/Mapping/ChunkColumn.cs index 22c9d2ae..5cbeda73 100644 --- a/MinecraftClient/Mapping/ChunkColumn.cs +++ b/MinecraftClient/Mapping/ChunkColumn.cs @@ -11,18 +11,29 @@ namespace MinecraftClient.Mapping /// public class ChunkColumn { - public const int ColumnSize = 16; + public int ColumnSize; + + public bool FullyLoaded = false; /// /// 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/Mapping/Dimension.cs b/MinecraftClient/Mapping/Dimension.cs new file mode 100644 index 00000000..987209ff --- /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 piglinSafe; + + /// + /// 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 ambientLight; + + + /// + /// 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 fixedTime = -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 respawnAnchorWorks; + + /// + /// Whether the dimension has skylight access or not. + /// + public readonly bool hasSkylight; + + /// + /// Whether players can use a bed to sleep. + /// + public readonly bool bedWorks; + + /// + /// 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 hasRaids; + + /// + /// The minimum Y level. + /// + public readonly int minY = 0; + + /// + /// The minimum Y level. + /// + public readonly int maxY = 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 logicalHeight; + + /// + /// The multiplier applied to coordinates when traveling to the dimension. + /// + public readonly double coordinateScale; + + /// + /// 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 hasCeiling; + + + /// + /// 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.piglinSafe = 1 == (byte)nbt["piglin_safe"]; + if (nbt.ContainsKey("natural")) + this.natural = 1 == (byte)nbt["natural"]; + if (nbt.ContainsKey("ambient_light")) + this.ambientLight = (float)nbt["ambient_light"]; + if (nbt.ContainsKey("fixed_time")) + this.fixedTime = (long)nbt["fixed_time"]; + if (nbt.ContainsKey("infiniburn")) + this.infiniburn = (string)nbt["infiniburn"]; + if (nbt.ContainsKey("respawn_anchor_works")) + this.respawnAnchorWorks = 1 == (byte)nbt["respawn_anchor_works"]; + if (nbt.ContainsKey("has_skylight")) + this.hasSkylight = 1 == (byte)nbt["has_skylight"]; + if (nbt.ContainsKey("bed_works")) + this.bedWorks = 1 == (byte)nbt["bed_works"]; + if (nbt.ContainsKey("effects")) + this.effects = (string)nbt["effects"]; + if (nbt.ContainsKey("has_raids")) + this.hasRaids = 1 == (byte)nbt["has_raids"]; + if (nbt.ContainsKey("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.maxY = this.minY + this.height; + if (nbt.ContainsKey("logical_height")) + this.logicalHeight = (int)nbt["logical_height"]; + if (nbt.ContainsKey("coordinate_scale")) + { + var coordinateScaleObj = nbt["coordinate_scale"]; + if (coordinateScaleObj.GetType() == typeof(float)) + this.coordinateScale = (float)coordinateScaleObj; + else + this.coordinateScale = (double)coordinateScaleObj; + } + if (nbt.ContainsKey("ultrawarm")) + this.ultrawarm = 1 == (byte)nbt["ultrawarm"]; + if (nbt.ContainsKey("has_ceiling")) + this.hasCeiling = 1 == (byte)nbt["has_ceiling"]; + } + + } +} diff --git a/MinecraftClient/Mapping/Location.cs b/MinecraftClient/Mapping/Location.cs index 00f18071..9dab848a 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); // below 1.16.2, Y coordinate always start from zero + else + return (int)Math.Floor((Y - world.GetDimension().minY) / Chunk.SizeY); } } @@ -299,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/Mapping/Movement.cs b/MinecraftClient/Mapping/Movement.cs index fc66ca7a..960921b7 100644 --- a/MinecraftClient/Mapping/Movement.cs +++ b/MinecraftClient/Mapping/Movement.cs @@ -33,7 +33,8 @@ namespace MinecraftClient.Mapping } 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 +263,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 9a57baae..39b5f0c4 100644 --- a/MinecraftClient/Mapping/World.cs +++ b/MinecraftClient/Mapping/World.cs @@ -16,11 +16,22 @@ namespace MinecraftClient.Mapping /// private Dictionary> chunks = new Dictionary>(); + /// + /// The dimension info of the world + /// + private Dimension dimension; + /// /// Lock for thread safety /// 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 /// @@ -78,6 +89,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 +211,7 @@ namespace MinecraftClient.Mapping try { chunks = new Dictionary>(); + dimension = null; } finally { diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 9d0fbf7c..b59ee3c8 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,11 @@ namespace MinecraftClient inventory.Items[slotId] = playerInventory.Items[-1]; playerInventory.Items.Remove(-1); } + + if (inventory.Items.ContainsKey(slotId)) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + else + changedSlots.Add(new Tuple((short)slotId, null)); } else { @@ -1307,6 +1311,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 +1391,10 @@ namespace MinecraftClient } } } + 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; @@ -1412,6 +1422,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 +1442,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, null)); } 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 +1470,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 +1500,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, null)); } 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 +1529,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])); } } } @@ -1508,18 +1543,26 @@ 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)); + } + 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 +2089,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; @@ -2490,7 +2536,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/MinecraftClient.csproj b/MinecraftClient/MinecraftClient.csproj index d104601e..46521de9 100644 --- a/MinecraftClient/MinecraftClient.csproj +++ b/MinecraftClient/MinecraftClient.csproj @@ -76,6 +76,7 @@ + diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index 6b2a1531..0b82a810 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -444,7 +444,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/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 685a2a0f..9b2dfade 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(); @@ -304,15 +304,20 @@ namespace MinecraftClient.Protocol.Handlers 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 +327,13 @@ 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) + handler.GetWorld().SetDimension(currentDimensionName, currentDimensionType); + if (protocolversion >= MC115Version) dataTypes.ReadNextLong(packetData); // Hashed world seed - 1.15 and above @@ -362,11 +372,12 @@ 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); this.currentDimension = 0; @@ -377,7 +388,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); // Dimension Name (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) @@ -425,56 +443,87 @@ 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 = null; + + if (protocolversion == MC117Version || protocolversion == MC1171Version) + verticalStripBitmask = dataTypes.ReadNextULongArray(packetData); // Bit Mask Length and Primary Bit Mask + + dataTypes.ReadNextNbt(packetData); // Heightmaps + + if (protocolversion == MC117Version || protocolversion == MC1171Version) + { + 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)); + handler.GetWorld().chunkCnt++; + handler.GetWorld().chunkLoadNotCompleted++; + pTerrain.ProcessChunkColumnData(chunkX, chunkZ, verticalStripBitmask, packetData); + handler.GetWorld().chunkLoadNotCompleted--; }).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; @@ -582,7 +631,7 @@ namespace MinecraftClient.Protocol.Handlers int sectionX = (int)(chunkSection >> 42); int sectionY = (int)((chunkSection << 44) >> 44); int sectionZ = (int)((chunkSection << 22) >> 42); - dataTypes.ReadNextBool(packetData); // Useless boolean + dataTypes.ReadNextBool(packetData); // Useless boolean (Related to light update) int blocksSize = dataTypes.ReadNextVarInt(packetData); for (int i = 0; i < blocksSize; i++) { @@ -701,6 +750,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; @@ -839,7 +892,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 +904,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,12 +916,12 @@ 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); Item item = dataTypes.ReadNextItemSlot(packetData, itemPalette); - handler.OnSetSlot(windowID, slotID, item); + handler.OnSetSlot(windowID, slotID, item, stateId); } break; case PacketTypesIn.WindowConfirmation: @@ -888,6 +944,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 +1044,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: @@ -1141,7 +1212,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) @@ -1646,7 +1717,12 @@ namespace MinecraftClient.Protocol.Handlers if (protocolversion >= MC19Version) fields.AddRange(dataTypes.GetVarInt(mainHand)); if (protocolversion >= MC117Version) - fields.Add(0); // Enables text filtering. Always false + { + 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); @@ -1895,7 +1971,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 { @@ -1932,81 +2008,47 @@ 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))); - log.Info("Held item: " + item.ToString()); - - packet.Add((byte)windowId); + packet.Add((byte)windowId); // Window ID // 1.18+ - if (protocolversion > MC1171Version) + 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+ + // 1.17+ Array of changed slots if (protocolversion >= MC117Version) { - try + packet.AddRange(dataTypes.GetVarInt(changedSlots.Count)); // Length of the array + foreach (var slot in changedSlots) { - foreach (KeyValuePair x in items) - { - log.Info("---------------------------------------------------"); - log.Info("ID: " + x.Key); - log.Info("Item: " + x.Value.ToString()); - - log.Info("Item NBT: "); - - if (x.Value.NBT.Count > 0) - { - foreach (string x2 in dataTypes.GetNbtAsString(x.Value.NBT, true)) - { - ConsoleIO.WriteLine(x2); - } - } - else - { - log.Info("\t- No NBT Data!"); - } - } + packet.AddRange(dataTypes.GetShort(slot.Item1)); // slot ID + packet.AddRange(dataTypes.GetItemSlot(slot.Item2, itemPalette)); // slot Data } - catch (Exception e) - { - ConsoleIO.WriteLine(e.StackTrace); - } - - 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.GetArray(arrayOfSlots)); - - packet.AddRange(dataTypes.GetVarInt(arrayOfSlots.Length)); - packet.AddRange(arrayOfSlots); } - packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); - - log.Info("Packet data: " + dataTypes.ByteArrayToString(packet.ToArray())); + packet.AddRange(dataTypes.GetItemSlot(item, itemPalette)); // Carried item (Clicked item) SendPacket(PacketTypesOut.ClickWindow, packet); return true; diff --git a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs index 14202599..73593da6 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18Terrain.cs @@ -28,7 +28,190 @@ namespace MinecraftClient.Protocol.Handlers } /// - /// Process chunk column data from the server and (un)load the chunk from the Minecraft world + /// 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, used in 1.17 and 1.17.1 + /// Cache for reading chunk data + public void ProcessChunkColumnData(int chunkX, int chunkZ, ulong[] verticalStripBitmask, Queue cache) + { + var world = handler.GetWorld(); + if (world.GetDimension() == null) + return; + + 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++) + { + // 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) + Chunk chunk = new Chunk(); + ReadBlockStatesField(ref chunk, cache); + + //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; + }); + + // Skip Read Biomes (Type: Paletted Container) - 1.18(1.18.1) and above + if (protocolversion >= Protocol18Handler.MC1181Version) + { + byte bitsPerEntryBiome = dataTypes.ReadNextByte(cache); // Bits Per Entry + if (bitsPerEntryBiome == 0) + { + 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) + } + handler.GetWorld()[chunkX, chunkZ].FullyLoaded = true; + } + + /// + /// 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 +223,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 +384,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 +408,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 +442,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 +470,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) { @@ -307,6 +491,7 @@ namespace MinecraftClient.Protocol.Handlers } } } + handler.GetWorld()[chunkX, chunkZ].FullyLoaded = 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 2b63d5e7..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. @@ -374,7 +375,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 diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index c6db23d3..c773e62c 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -314,11 +314,13 @@ 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. 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 cmd.reco.desc=restart and reconnect to the server.