diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index da7cbc92..e19a0260 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -54,6 +54,7 @@ namespace MinecraftClient private readonly List bots = new(); private static readonly List botsOnHold = new(); private static readonly Dictionary inventories = new(); + private static readonly HashSet inventoriesWithFullContents = new(); private readonly Dictionary unlockedRecipes = new(StringComparer.Ordinal); private readonly Dictionary achievements = new(StringComparer.Ordinal); private string? activeAdvancementTab; @@ -2004,8 +2005,8 @@ namespace MinecraftClient if (item.Count <= spaceLeft) { // Can fit into the stack - item.Count = 0; curItem.Count += item.Count; + item.Count = 0; changedSlots.Add(new Tuple((short)curId, curItem)); changedSlots.Add(new Tuple((short)slotId, null)); @@ -2067,12 +2068,12 @@ namespace MinecraftClient firstWindowSlot = -1; lastWindowSlot = -1; - if (inventory.Type == ContainerType.PlayerInventory) + if (inventory.Type is ContainerType.PlayerInventory or ContainerType.Unknown) return false; const int mirroredPlayerInventorySlotCount = 36; int slotCount = inventory.Type.SlotCount(); - if (slotCount < mirroredPlayerInventorySlotCount) + if (slotCount <= mirroredPlayerInventorySlotCount) return false; firstWindowSlot = slotCount - mirroredPlayerInventorySlotCount; @@ -2080,20 +2081,6 @@ namespace MinecraftClient return true; } - private static bool TryGetMirroredPlayerInventorySlot(Container inventory, int windowSlot, out int playerInventorySlot) - { - playerInventorySlot = -1; - - if (!TryGetMirroredPlayerInventoryRange(inventory, out int firstWindowSlot, out int lastWindowSlot)) - return false; - - if (windowSlot < firstWindowSlot || windowSlot > lastWindowSlot) - return false; - - playerInventorySlot = windowSlot - firstWindowSlot + 9; - return true; - } - private static bool AreSameInventorySlot(Item? left, Item? right) { if (left is null || left.IsEmpty) @@ -2113,35 +2100,67 @@ namespace MinecraftClient if (!inventories.TryGetValue(0, out Container? playerInventory)) return false; + if (item is null || item.IsEmpty) + return playerInventory.Items.Remove(playerInventorySlot); + + Item itemClone = item.CloneWithCount(item.Count); + playerInventory.Items.TryGetValue(playerInventorySlot, out Item? previousItem); - if (AreSameInventorySlot(previousItem, item)) + if (AreSameInventorySlot(previousItem, itemClone)) return false; - if (item is null || item.IsEmpty) - playerInventory.Items.Remove(playerInventorySlot); - else - playerInventory.Items[playerInventorySlot] = item; + playerInventory.Items[playerInventorySlot] = itemClone; return true; } - private bool SyncPlayerInventorySlotFromWindow(Container inventory, int windowSlot) + private bool SyncPlayerInventorySlotsFromWindow(Container? inventory) { - if (!TryGetMirroredPlayerInventorySlot(inventory, windowSlot, out int playerInventorySlot)) + if (inventory is null) return false; - inventory.Items.TryGetValue(windowSlot, out Item? item); - return SetPlayerInventorySlot(playerInventorySlot, item); - } + if (!inventoriesWithFullContents.Contains(inventory.ID)) + return false; - private bool SyncPlayerInventorySlotsFromWindow(Container inventory) - { if (!TryGetMirroredPlayerInventoryRange(inventory, out int firstWindowSlot, out int lastWindowSlot)) return false; - bool changed = false; + if (!inventories.TryGetValue(0, out Container? playerInventory)) + return false; + + const int firstPlayerInventorySlot = 9; + const int lastPlayerInventorySlot = firstPlayerInventorySlot + 36 - 1; + Dictionary mirroredItems = new(); + for (int windowSlot = firstWindowSlot; windowSlot <= lastWindowSlot; windowSlot++) - changed |= SyncPlayerInventorySlotFromWindow(inventory, windowSlot); + { + if (!inventory.Items.TryGetValue(windowSlot, out Item? item) || item.IsEmpty) + continue; + + int playerInventorySlot = windowSlot - firstWindowSlot + firstPlayerInventorySlot; + mirroredItems[playerInventorySlot] = item.CloneWithCount(item.Count); + } + + bool changed = false; + for (int playerInventorySlot = firstPlayerInventorySlot; playerInventorySlot <= lastPlayerInventorySlot; playerInventorySlot++) + { + playerInventory.Items.TryGetValue(playerInventorySlot, out Item? previousItem); + mirroredItems.TryGetValue(playerInventorySlot, out Item? mirroredItem); + if (AreSameInventorySlot(previousItem, mirroredItem)) + continue; + + changed = true; + break; + } + + if (!changed) + return false; + + for (int playerInventorySlot = firstPlayerInventorySlot; playerInventorySlot <= lastPlayerInventorySlot; playerInventorySlot++) + playerInventory.Items.Remove(playerInventorySlot); + + foreach ((int playerInventorySlot, Item item) in mirroredItems) + playerInventory.Items[playerInventorySlot] = item; return changed; } @@ -2210,6 +2229,10 @@ namespace MinecraftClient playerInventory.Items.Remove(-1); } + // Clean up cursor item if count reached zero + if (playerInventory.Items.TryGetValue(-1, out Item? cursorAfterLeft) && cursorAfterLeft.IsEmpty) + playerInventory.Items.Remove(-1); + if (inventory.Items.ContainsKey(slotId)) changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); else @@ -2266,6 +2289,10 @@ namespace MinecraftClient inventory.Items[slotId] = itemClone; playerInventory.Items[-1].Count--; } + + // Clean up cursor item if count reached zero + if (playerInventory.Items.TryGetValue(-1, out Item? cursorItem) && cursorItem.IsEmpty) + playerInventory.Items.Remove(-1); } else { @@ -2851,6 +2878,11 @@ namespace MinecraftClient changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } } + if (item!.Count <= 0 && inventory.Items.ContainsKey(slotId)) + { + inventory.Items.Remove(slotId); + changedSlots.Add(new Tuple((short)slotId, null)); + } } break; case WindowActionType.DropItem: @@ -2872,10 +2904,10 @@ namespace MinecraftClient changedSlots.Add(new Tuple((short)slotId, null)); break; } - - SyncPlayerInventorySlotsFromWindow(inventory); } + SyncPlayerInventorySlotsFromWindow(inventory); + return handler.SendWindowAction(windowId, slotId, action, item, changedSlots, inventories[windowId].StateID); } @@ -2926,7 +2958,10 @@ namespace MinecraftClient if (inventories.ContainsKey(windowId)) { if (windowId != 0) + { inventories.Remove(windowId); + inventoriesWithFullContents.Remove(windowId); + } bool result = handler.SendCloseWindow(windowId); DispatchBotEvent(bot => bot.OnInventoryClose(windowId)); return result; @@ -2947,6 +2982,7 @@ namespace MinecraftClient return InvokeOnMainThread(ClearInventories); inventories.Clear(); + inventoriesWithFullContents.Clear(); inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory"); ClearUnlockedRecipes(); return true; @@ -3759,6 +3795,7 @@ namespace MinecraftClient /// Inventory ID public void OnInventoryOpen(int inventoryID, Container inventory) { + inventoriesWithFullContents.Remove(inventoryID); inventories[inventoryID] = inventory; if (inventoryID != 0) @@ -3785,9 +3822,15 @@ namespace MinecraftClient if (inventories.ContainsKey(inventoryID)) { if (inventoryID == 0) + { inventories[0].Items.Clear(); // Don't delete player inventory + inventoriesWithFullContents.Clear(); + } else + { inventories.Remove(inventoryID); + inventoriesWithFullContents.Remove(inventoryID); + } } if (inventoryID != 0) @@ -3913,8 +3956,13 @@ namespace MinecraftClient { if (inventories.ContainsKey(inventoryID)) { + // Filter out empty items (Count=0 or Air) that some servers may send + foreach (int key in itemList.Where(slot => slot.Value.IsEmpty).Select(slot => slot.Key).ToList()) + itemList.Remove(key); + inventories[inventoryID].Items = itemList; inventories[inventoryID].StateID = stateId; + inventoriesWithFullContents.Add(inventoryID); bool playerInventoryChanged = SyncPlayerInventorySlotsFromWindow(inventories[inventoryID]); if (playerInventoryChanged) DispatchBotEvent(bot => bot.OnInventoryUpdate(0)); @@ -3942,7 +3990,7 @@ namespace MinecraftClient inventoryID = 0; // Prevent key not found for some bots relied to this event if (inventories.ContainsKey(0)) { - if (item is not null) + if (item is not null && !item.IsEmpty) inventories[0].Items[-1] = item; else inventories[0].Items.Remove(-1); @@ -3959,7 +4007,7 @@ namespace MinecraftClient } else inventories[inventoryID].Items[slotID] = item; - if (SyncPlayerInventorySlotFromWindow(inventories[inventoryID], slotID)) + if (SyncPlayerInventorySlotsFromWindow(inventories[inventoryID])) DispatchBotEvent(bot => bot.OnInventoryUpdate(0)); } } diff --git a/MinecraftClient/Resources/Translations/Translations.Designer.cs b/MinecraftClient/Resources/Translations/Translations.Designer.cs index 52ec91ae..2330a891 100644 --- a/MinecraftClient/Resources/Translations/Translations.Designer.cs +++ b/MinecraftClient/Resources/Translations/Translations.Designer.cs @@ -4135,7 +4135,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Shift clicking slot {0} in window #{1}. + /// Looks up a localized string similar to Shift. /// internal static string cmd_inventory_shiftclick { get { @@ -4153,7 +4153,7 @@ namespace MinecraftClient { } /// - /// Looks up a localized string similar to Shift right-clicking slot {0} in window #{1}. + /// Looks up a localized string similar to Shift right. /// internal static string cmd_inventory_shiftrightclick { get { diff --git a/MinecraftClient/Resources/Translations/Translations.resx b/MinecraftClient/Resources/Translations/Translations.resx index 6052e661..cb2dae94 100644 --- a/MinecraftClient/Resources/Translations/Translations.resx +++ b/MinecraftClient/Resources/Translations/Translations.resx @@ -1457,7 +1457,7 @@ Note that parameters in '[]' are optional. Right - Shift clicking slot {0} in window #{1} + Shift Shift click failed, this may be because this container type is not supported @@ -2329,7 +2329,7 @@ Logging in... Minimum number that you have provided is bigger than the maximum, swapping them around! - Shift right-clicking slot {0} in window #{1} + Shift right Avaliable profiles: