diff --git a/MinecraftClient/Commands/Inventory.cs b/MinecraftClient/Commands/Inventory.cs index b164662a..7245fec4 100644 --- a/MinecraftClient/Commands/Inventory.cs +++ b/MinecraftClient/Commands/Inventory.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Text; using MinecraftClient.Inventory; namespace MinecraftClient.Commands @@ -11,224 +12,225 @@ namespace MinecraftClient.Commands public override string CmdUsage { get { return GetBasicUsage(); } } public override string CmdDesc { get { return "cmd.inventory.desc"; } } - public override string Run(McClient handler, string command, Dictionary localVars) + public override string Run(McClient handler, string command, Dictionary? localVars) { if (handler.GetInventoryEnabled()) { string[] args = getArgs(command); if (args.Length >= 1) { - try + int inventoryId; + if (args[0].ToLower() == "creativegive") { - int inventoryId; - if (args[0].ToLower() == "creativegive") + if (args.Length >= 4) { - if (args.Length >= 4) + if (!int.TryParse(args[1], out int slot)) + return GetCmdDescTranslated(); + + if (Enum.TryParse(args[2], true, out ItemType itemType)) { - int slot = int.Parse(args[1]); - ItemType itemType = ItemType.Stone; - if (Enum.TryParse(args[2], true, out itemType)) - { - if (handler.GetGamemode() == 1) - { - int count = int.Parse(args[3]); - if (handler.DoCreativeGive(slot, itemType, count, null)) - return Translations.Get("cmd.inventory.creative_done", itemType, count, slot); - else return Translations.Get("cmd.inventory.creative_fail"); - } - else return Translations.Get("cmd.inventory.need_creative"); - } - else - { - return GetCmdDescTranslated(); - } - } - else return GetCmdDescTranslated(); - } - else if (args[0].ToLower() == "creativedelete") - { - if (args.Length >= 2) - { - int slot = int.Parse(args[1]); if (handler.GetGamemode() == 1) { - if (handler.DoCreativeGive(slot, ItemType.Null, 0, null)) - return Translations.Get("cmd.inventory.creative_delete", slot); - else return Translations.Get("cmd.inventory.creative_fail"); - } - else return Translations.Get("cmd.inventory.need_creative"); - } - else return GetCmdDescTranslated(); - } - else if (args[0].ToLower().StartsWith("p")) - { - // player inventory is always ID 0 - inventoryId = 0; - } - else if (args[0].ToLower().StartsWith("c")) - { - List availableIds = handler.GetInventories().Keys.ToList(); - availableIds.Remove(0); // remove player inventory ID from list - if (availableIds.Count > 0) - inventoryId = availableIds.Max(); // use foreground container - else return Translations.Get("cmd.inventory.container_not_found"); - } - else if (args[0].ToLower() == "help") - { - if (args.Length >= 2) - { - return GetSubCommandHelp(args[1]); - } - else return GetHelp(); - } - else inventoryId = int.Parse(args[0]); - string action = args.Length > 1 - ? args[1].ToLower() - : "list"; - switch (action) - { - case "close": - if (handler.CloseInventory(inventoryId)) - return Translations.Get("cmd.inventory.close", inventoryId); - else return Translations.Get("cmd.inventory.close_fail", inventoryId); - case "list": - Container inventory = handler.GetInventory(inventoryId); - if (inventory == null) - return Translations.Get("cmd.inventory.not_exist", inventoryId); - SortedDictionary itemsSorted = new SortedDictionary(inventory.Items); - List response = new List(); - response.Add(Translations.Get("cmd.inventory.inventory") + " #" + inventoryId + " - " + inventory.Title + "§8"); - string asciiArt = inventory.Type.GetAsciiArt(); - if (asciiArt != null && Settings.DisplayInventoryLayout) - response.Add(asciiArt); - int selectedHotbar = handler.GetCurrentSlot() + 1; - foreach (KeyValuePair item in itemsSorted) - { - int hotbar; - bool isHotbar = inventory.IsHotbar(item.Key, out hotbar); - string hotbarString = isHotbar ? (hotbar + 1).ToString() : " "; - if ((hotbar + 1) == selectedHotbar) - hotbarString = ">" + hotbarString; - response.Add(String.Format("{0,2} | #{1,-2}: {2}", hotbarString, item.Key, item.Value.ToString())); - } - if (inventoryId == 0) - response.Add(Translations.Get("cmd.inventory.hotbar", (handler.GetCurrentSlot() + 1))); - return String.Join("\n", response.ToArray()); - case "click": - if (args.Length >= 3) - { - int slot = int.Parse(args[2]); - WindowActionType actionType = WindowActionType.LeftClick; - string keyName = "cmd.inventory.left"; - if (args.Length >= 4) - { - string b = args[3]; - if (b.ToLower()[0] == 'r') - { - actionType = WindowActionType.RightClick; - keyName = "cmd.inventory.right"; - } - if (b.ToLower()[0] == 'm') - { - actionType = WindowActionType.MiddleClick; - keyName = "cmd.inventory.middle"; - } - } - handler.DoWindowAction(inventoryId, slot, actionType); - return Translations.Get("cmd.inventory.clicking", Translations.Get(keyName), slot, inventoryId); - } - else return CmdUsage; - case "drop": - if (args.Length >= 3) - { - int slot = int.Parse(args[2]); - // check item exist - if (!handler.GetInventory(inventoryId).Items.ContainsKey(slot)) - return Translations.Get("cmd.inventory.no_item", slot); - WindowActionType actionType = WindowActionType.DropItem; - if (args.Length >= 4) - { - if (args[3].ToLower() == "all") - { - actionType = WindowActionType.DropItemStack; - } - } - if (handler.DoWindowAction(inventoryId, slot, actionType)) - { - if (actionType == WindowActionType.DropItemStack) - return Translations.Get("cmd.inventory.drop_stack", slot); - else return Translations.Get("cmd.inventory.drop", slot); - } + if (!int.TryParse(args[3], out int count)) + return GetCmdDescTranslated(); + + if (handler.DoCreativeGive(slot, itemType, count, null)) + return Translations.Get("cmd.inventory.creative_done", itemType, count, slot); else - { - return "Failed"; - } + return Translations.Get("cmd.inventory.creative_fail"); } - else return GetCmdDescTranslated(); - default: + else + return Translations.Get("cmd.inventory.need_creative"); + } + else return GetCmdDescTranslated(); } + else + return GetCmdDescTranslated(); } - catch (FormatException) { return GetCmdDescTranslated(); } + else if (args[0].ToLower() == "creativedelete") + { + if (args.Length >= 2) + { + if (!int.TryParse(args[1], out int slot)) + return GetCmdDescTranslated(); + + if (handler.GetGamemode() == 1) + { + if (handler.DoCreativeGive(slot, ItemType.Null, 0, null)) + return Translations.Get("cmd.inventory.creative_delete", slot); + else + return Translations.Get("cmd.inventory.creative_fail"); + } + else + return Translations.Get("cmd.inventory.need_creative"); + } + else + return GetCmdDescTranslated(); + } + else if (args[0].ToLower().StartsWith("p")) + { + // player inventory is always ID 0 + inventoryId = 0; + } + else if (args[0].ToLower().StartsWith("c")) + { + List availableIds = handler.GetInventories().Keys.ToList(); + availableIds.Remove(0); // remove player inventory ID from list + if (availableIds.Count > 0) + inventoryId = availableIds.Max(); // use foreground container + else + return Translations.Get("cmd.inventory.container_not_found"); + } + else if (args[0].ToLower() == "help") + { + if (args.Length >= 2) + return GetSubCommandHelp(args[1]); + else + return GetHelp(); + } + else if (!int.TryParse(args[0], out inventoryId)) + return GetCmdDescTranslated(); + + Container? inventory = handler.GetInventory(inventoryId); + if (inventory == null) + return Translations.Get("cmd.inventory.not_exist", inventoryId); + + string action = args.Length > 1 ? args[1].ToLower() : "list"; + if (action == "close") + { + if (handler.CloseInventory(inventoryId)) + return Translations.Get("cmd.inventory.close", inventoryId); + else + return Translations.Get("cmd.inventory.close_fail", inventoryId); + } + else if (action == "list") + { + StringBuilder response = new(); + response.Append(Translations.Get("cmd.inventory.inventory")); + response.AppendLine(String.Format(" #{0} - {1}§8", inventoryId, inventory.Title)); + + string asciiArt = inventory.Type.GetAsciiArt(); + if (asciiArt != null && Settings.DisplayInventoryLayout) + response.AppendLine(asciiArt); + + int selectedHotbar = handler.GetCurrentSlot() + 1; + foreach ((int itemId, Item item) in new SortedDictionary(inventory.Items)) + { + bool isHotbar = inventory.IsHotbar(itemId, out int hotbar); + string hotbarString = isHotbar ? (hotbar + 1).ToString() : " "; + if ((hotbar + 1) == selectedHotbar) + hotbarString = ">" + hotbarString; + response.AppendLine(String.Format("{0,2} | #{1,-2}: {2}", hotbarString, itemId, item.ToString())); + } + + if (inventoryId == 0) + response.AppendLine(Translations.Get("cmd.inventory.hotbar", (handler.GetCurrentSlot() + 1))); + + response.Remove(response.Length - 1, 1); // Remove last '\n' + return response.ToString(); + } + else if (action == "click" && args.Length >= 3) + { + if (!int.TryParse(args[2], out int slot)) + return GetCmdDescTranslated(); + + WindowActionType actionType = WindowActionType.LeftClick; + string keyName = "cmd.inventory.left"; + if (args.Length >= 4) + { + string b = args[3]; + if (b.ToLower()[0] == 'r') + (actionType, keyName) = (WindowActionType.RightClick, "cmd.inventory.right"); + else if (b.ToLower()[0] == 'm') + (actionType, keyName) = (WindowActionType.MiddleClick, "cmd.inventory.middle"); + } + + handler.DoWindowAction(inventoryId, slot, actionType); + return Translations.Get("cmd.inventory.clicking", Translations.Get(keyName), slot, inventoryId); + } + else if (action == "shiftclick" && args.Length >= 3) + { + if (!int.TryParse(args[2], out int slot)) + return GetCmdDescTranslated(); + + if (!handler.DoWindowAction(inventoryId, slot, WindowActionType.ShiftClick)) + return Translations.Get("cmd.inventory.shiftclick_fail"); + + return Translations.Get("cmd.inventory.shiftclick", slot, inventoryId); + } + else if (action == "drop" && args.Length >= 3) + { + if (!int.TryParse(args[2], out int slot)) + return GetCmdDescTranslated(); + + // check item exist + if (!inventory.Items.ContainsKey(slot)) + return Translations.Get("cmd.inventory.no_item", slot); + + WindowActionType actionType = WindowActionType.DropItem; + if (args.Length >= 4 && args[3].ToLower() == "all") + actionType = WindowActionType.DropItemStack; + + if (handler.DoWindowAction(inventoryId, slot, actionType)) + { + if (actionType == WindowActionType.DropItemStack) + return Translations.Get("cmd.inventory.drop_stack", slot); + else + return Translations.Get("cmd.inventory.drop", slot); + } + else + return "Failed"; + } + else + return GetCmdDescTranslated(); } else { - Dictionary inventories = handler.GetInventories(); - List response = new List(); - response.Add(Translations.Get("cmd.inventory.inventories") + ":"); - foreach (KeyValuePair inventory in inventories) - { - response.Add(String.Format(" #{0}: {1}", inventory.Key, inventory.Value.Title + "§8")); - } - response.Add(CmdUsage); - return String.Join("\n", response); + StringBuilder response = new(); + response.AppendLine(Translations.Get("cmd.inventory.inventories")).Append(':'); + foreach ((int invId, Container inv) in handler.GetInventories()) + response.AppendLine(String.Format(" #{0}: {1}§8", invId, inv.Title)); + response.Append(CmdUsage); + return response.ToString(); } } - else return Translations.Get("extra.inventory_required"); + else + return Translations.Get("extra.inventory_required"); } #region Methods for commands help - private string GetCommandDesc() - { - return GetBasicUsage() + " Type \"/inventory help\" for more help"; - } - private string GetAvailableActions() + private static string GetAvailableActions() { return Translations.Get("cmd.inventory.help.available") + ": list, close, click, drop, creativegive, creativedelete."; } - private string GetBasicUsage() + private static string GetBasicUsage() { return Translations.Get("cmd.inventory.help.basic") + ": /inventory > ."; } - private string GetHelp() + private static string GetHelp() { return Translations.Get("cmd.inventory.help.help", GetAvailableActions()); } - private string GetSubCommandHelp(string cmd) + private static string GetSubCommandHelp(string cmd) { - switch (cmd) + string usageStr = ' ' + Translations.Get("cmd.inventory.help.usage") + ": "; + return cmd switch { - case "list": - return Translations.Get("cmd.inventory.help.list") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory > list"; - case "close": - return Translations.Get("cmd.inventory.help.close") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory > close"; - case "click": - return Translations.Get("cmd.inventory.help.click") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory > click [left|right|middle]. \nDefault is left click"; - case "drop": - return Translations.Get("cmd.inventory.help.drop") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory > drop [all]. \nAll means drop full stack"; - case "creativegive": - return Translations.Get("cmd.inventory.help.creativegive") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory creativegive "; - case "creativedelete": - return Translations.Get("cmd.inventory.help.creativedelete") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory creativedelete "; - case "help": - return GetHelp(); - default: - return Translations.Get("cmd.inventory.help.unknown") + GetAvailableActions(); - } + "list" => Translations.Get("cmd.inventory.help.list") + usageStr + "/inventory > list", + "close" => Translations.Get("cmd.inventory.help.close") + usageStr + "/inventory > close", + "click" => Translations.Get("cmd.inventory.help.click") + usageStr + "/inventory > click [left|right|middle]\nDefault is left click", + "shiftclick" => Translations.Get("cmd.inventory.help.shiftclick") + usageStr + "/inventory > shiftclick ", + "drop" => Translations.Get("cmd.inventory.help.drop") + usageStr + "/inventory > drop [all]\nAll means drop full stack", + "creativegive" => Translations.Get("cmd.inventory.help.creativegive") + usageStr + "/inventory creativegive ", + "creativedelete" => Translations.Get("cmd.inventory.help.creativedelete") + usageStr + "/inventory creativedelete ", + "help" => GetHelp(), + _ => Translations.Get("cmd.inventory.help.unknown") + GetAvailableActions(), + }; } #endregion } diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 53a92a89..62ca784a 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -1027,7 +1027,7 @@ namespace MinecraftClient /// /// Window ID of the requested inventory /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) - public Container GetInventory(int inventoryID) + public Container? GetInventory(int inventoryID) { if (InvokeRequired) return InvokeOnMainThread(() => GetInventory(inventoryID)); @@ -1043,7 +1043,7 @@ namespace MinecraftClient /// Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID) public Container GetPlayerInventory() { - return GetInventory(0); + return GetInventory(0)!; } /// @@ -1277,6 +1277,63 @@ namespace MinecraftClient return InvokeOnMainThread(() => handler.SendUseItem(0, this.sequenceId)); } + /// + /// Try to merge a slot + /// + /// The container where the item is located + /// Items to be processed + /// The ID of the slot of the item to be processed + /// The slot that was put down + /// The ID of the slot being put down + /// Record changes + /// Whether to fully merge + private static bool TryMergeSlot(Container inventory, Item item, int slotId, Item curItem, int curId, List> changedSlots) + { + int spaceLeft = curItem.Type.StackCount() - curItem.Count; + if (curItem.Type == item!.Type && spaceLeft > 0) + { + // Put item on that stack + if (item.Count <= spaceLeft) + { + // Can fit into the stack + item.Count = 0; + curItem.Count += item.Count; + + changedSlots.Add(new Tuple((short)curId, curItem)); + changedSlots.Add(new Tuple((short)slotId, null)); + + inventory.Items.Remove(slotId); + return true; + } + else + { + item.Count -= spaceLeft; + curItem.Count += spaceLeft; + + changedSlots.Add(new Tuple((short)curId, curItem)); + } + } + return false; + } + + /// + /// Store items in a new slot + /// + /// The container where the item is located + /// Items to be processed + /// The ID of the slot of the item to be processed + /// ID of the new slot + /// Record changes + private static void StoreInNewSlot(Container inventory, Item item, int slotId, int newSlotId, List> changedSlots) + { + Item newItem = new(item.Type, item.Count, item.NBT); + inventory.Items[newSlotId] = newItem; + inventory.Items.Remove(slotId); + + changedSlots.Add(new Tuple((short)newSlotId, newItem)); + changedSlots.Add(new Tuple((short)slotId, null)); + } + /// /// Click a slot in the specified window /// @@ -1286,15 +1343,15 @@ namespace MinecraftClient if (InvokeRequired) return InvokeOnMainThread(() => DoWindowAction(windowId, slotId, action)); - Item item = null; + Item? item = null; if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId)) item = inventories[windowId].Items[slotId]; - List> changedSlots = new List>(); // List + List> changedSlots = new(); // List // Update our inventory base on action type - var inventory = GetInventory(windowId); - var playerInventory = GetInventory(0); + Container inventory = GetInventory(windowId)!; + Container playerInventory = GetInventory(0)!; if (inventory != null) { switch (action) @@ -1343,9 +1400,9 @@ namespace MinecraftClient } if (inventory.Items.ContainsKey(slotId)) - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); else - changedSlots.Add(new Tuple((short)slotId, null)); + changedSlots.Add(new Tuple((short)slotId, null)); } else { @@ -1359,7 +1416,7 @@ namespace MinecraftClient playerInventory.Items[-1] = inventory.Items[slotId]; inventory.Items.Remove(slotId); - changedSlots.Add(new Tuple((short)slotId, null)); + changedSlots.Add(new Tuple((short)slotId, null)); } } break; @@ -1439,30 +1496,455 @@ namespace MinecraftClient } } if (inventory.Items.ContainsKey(slotId)) - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); else - changedSlots.Add(new Tuple((short)slotId, null)); + changedSlots.Add(new Tuple((short)slotId, null)); break; case WindowActionType.ShiftClick: if (slotId == 0) break; - if (inventory.Items.ContainsKey(slotId)) + if (item != null) { /* Target slot have item */ + bool lower2upper = false, upper2backpack = false, backpack2hotbar = false; // mutual exclusion + bool hotbarFirst = true; // Used when upper2backpack = true int upperStartSlot = 9; int upperEndSlot = 35; + int lowerStartSlot = 36; switch (inventory.Type) { case ContainerType.PlayerInventory: - upperStartSlot = 9; - upperEndSlot = 35; + if (slotId >= 0 && slotId <= 8 || slotId == 45) + { + if (slotId != 0) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 9; + } + else if (item != null && false /* Check if wearable */) + { + lower2upper = true; + // upperStartSlot = ?; + // upperEndSlot = ?; + // Todo: Distinguish the type of equipment + } + else + { + if (slotId >= 9 && slotId <= 35) + { + backpack2hotbar = true; + lowerStartSlot = 36; + } + else + { + lower2upper = true; + upperStartSlot = 9; + upperEndSlot = 35; + } + } + break; + case ContainerType.Generic_9x1: + if (slotId >= 0 && slotId <= 8) + { + upper2backpack = true; + lowerStartSlot = 9; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 8; + } + break; + case ContainerType.Generic_9x2: + if (slotId >= 0 && slotId <= 17) + { + upper2backpack = true; + lowerStartSlot = 18; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 17; + } + break; + case ContainerType.Generic_9x3: + case ContainerType.ShulkerBox: + if (slotId >= 0 && slotId <= 26) + { + upper2backpack = true; + lowerStartSlot = 27; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 26; + } + break; + case ContainerType.Generic_9x4: + if (slotId >= 0 && slotId <= 35) + { + upper2backpack = true; + lowerStartSlot = 36; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 35; + } + break; + case ContainerType.Generic_9x5: + if (slotId >= 0 && slotId <= 44) + { + upper2backpack = true; + lowerStartSlot = 45; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 44; + } + break; + case ContainerType.Generic_9x6: + if (slotId >= 0 && slotId <= 53) + { + upper2backpack = true; + lowerStartSlot = 54; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 53; + } + break; + case ContainerType.Generic_3x3: + if (slotId >= 0 && slotId <= 8) + { + upper2backpack = true; + lowerStartSlot = 9; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 8; + } + break; + case ContainerType.Anvil: + if (slotId >= 0 && slotId <= 2) + { + if (slotId >= 0 && slotId <= 1) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 3; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 1; + } + break; + case ContainerType.Beacon: + if (slotId == 0) + { + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 1; + } + else if (item != null && item.Count == 1 && (item.Type == ItemType.NetheriteIngot || + item.Type == ItemType.Emerald || item.Type == ItemType.Diamond || item.Type == ItemType.GoldIngot || + item.Type == ItemType.IronIngot) && !inventory.Items.ContainsKey(0)) + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 0; + } + else + { + if (slotId >= 1 && slotId <= 27) + { + backpack2hotbar = true; + lowerStartSlot = 28; + } + else + { + lower2upper = true; + upperStartSlot = 1; + upperEndSlot = 27; + } + } + break; + case ContainerType.BlastFurnace: + case ContainerType.Furnace: + case ContainerType.Smoker: + if (slotId >= 0 && slotId <= 2) + { + if (slotId >= 0 && slotId <= 1) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 3; + } + else if (item != null && false /* Check if it can be burned */) + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 0; + } + else + { + if (slotId >= 3 && slotId <= 29) + { + backpack2hotbar = true; + lowerStartSlot = 30; + } + else + { + lower2upper = true; + upperStartSlot = 3; + upperEndSlot = 29; + } + } + break; + case ContainerType.BrewingStand: + if (slotId >= 0 && slotId <= 3) + { + upper2backpack = true; + lowerStartSlot = 5; + } + else if (item != null && item.Type == ItemType.BlazePowder) + { + lower2upper = true; + if (!inventory.Items.ContainsKey(4) || inventory.Items[4].Count < 64) + upperStartSlot = upperEndSlot = 4; + else + upperStartSlot = upperEndSlot = 3; + } + else if (item != null && false /* Check if it can be used for alchemy */) + { + lower2upper = true; + upperStartSlot = upperEndSlot = 3; + } + else if (item != null && (item.Type == ItemType.Potion || item.Type == ItemType.GlassBottle)) + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 2; + } + else + { + if (slotId >= 5 && slotId <= 31) + { + backpack2hotbar = true; + lowerStartSlot = 32; + } + else + { + lower2upper = true; + upperStartSlot = 5; + upperEndSlot = 31; + } + } break; case ContainerType.Crafting: - upperStartSlot = 1; - upperEndSlot = 9; + if (slotId >= 0 && slotId <= 9) + { + if (slotId >= 1 && slotId <= 9) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 10; + } + else + { + lower2upper = true; + upperStartSlot = 1; + upperEndSlot = 9; + } break; - // TODO: Define more container type here + case ContainerType.Enchantment: + if (slotId >= 0 && slotId <= 1) + { + upper2backpack = true; + lowerStartSlot = 5; + } + else if (item != null && item.Type == ItemType.LapisLazuli) + { + lower2upper = true; + upperStartSlot = upperEndSlot = 1; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 0; + } + break; + case ContainerType.Grindstone: + if (slotId >= 0 && slotId <= 2) + { + if (slotId >= 0 && slotId <= 1) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 3; + } + else if (item != null && false /* Check */) + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 1; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 1; + } + break; + case ContainerType.Hopper: + if (slotId >= 0 && slotId <= 4) + { + upper2backpack = true; + lowerStartSlot = 5; + } + else + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 4; + } + break; + case ContainerType.Lectern: + return false; + break; + case ContainerType.Loom: + if (slotId >= 0 && slotId <= 3) + { + if (slotId >= 0 && slotId <= 5) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 4; + } + else if (item != null && false /* Check for availability for staining */) + { + lower2upper = true; + // upperStartSlot = ?; + // upperEndSlot = ?; + } + else + { + if (slotId >= 4 && slotId <= 30) + { + backpack2hotbar = true; + lowerStartSlot = 31; + } + else + { + lower2upper = true; + upperStartSlot = 4; + upperEndSlot = 30; + } + } + break; + case ContainerType.Merchant: + if (slotId >= 0 && slotId <= 2) + { + if (slotId >= 0 && slotId <= 1) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 3; + } + else if (item != null && false /* Check if it is available for trading */) + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 1; + } + else + { + if (slotId >= 3 && slotId <= 29) + { + backpack2hotbar = true; + lowerStartSlot = 30; + } + else + { + lower2upper = true; + upperStartSlot = 3; + upperEndSlot = 29; + } + } + break; + case ContainerType.Cartography: + if (slotId >= 0 && slotId <= 2) + { + if (slotId >= 0 && slotId <= 1) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 3; + } + else if (item != null && item.Type == ItemType.FilledMap) + { + lower2upper = true; + upperStartSlot = upperEndSlot = 0; + } + else if (item != null && item.Type == ItemType.Map) + { + lower2upper = true; + upperStartSlot = upperEndSlot = 1; + } + else + { + if (slotId >= 3 && slotId <= 29) + { + backpack2hotbar = true; + lowerStartSlot = 30; + } + else + { + lower2upper = true; + upperStartSlot = 3; + upperEndSlot = 29; + } + } + break; + case ContainerType.Stonecutter: + if (slotId >= 0 && slotId <= 1) + { + if (slotId == 0) + hotbarFirst = false; + upper2backpack = true; + lowerStartSlot = 2; + } + else if (item != null && false /* Check if it is available for stone cutteing */) + { + lower2upper = true; + upperStartSlot = 0; + upperEndSlot = 0; + } + else + { + if (slotId >= 2 && slotId <= 28) + { + backpack2hotbar = true; + lowerStartSlot = 29; + } + else + { + lower2upper = true; + upperStartSlot = 2; + upperEndSlot = 28; + } + } + break; + // TODO: Define more container type here + default: + return false; } // Cursor have item or not doesn't matter @@ -1470,120 +1952,94 @@ namespace MinecraftClient // 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) + if (lower2upper) { - // Clicked slot is on upper side inventory, put it to hotbar - // Now try to find same item and put on them - var itemsClone = playerInventory.Items.ToDictionary(entry => entry.Key, entry => entry.Value); - foreach (KeyValuePair _item in itemsClone) + int firstEmptySlot = -1; + for (int i = upperStartSlot; i <= upperEndSlot; ++i) { - if (_item.Key <= upperEndSlot) continue; - - int maxCount = _item.Value.Type.StackCount(); - if (_item.Value.Type == inventory.Items[slotId].Type && _item.Value.Count < maxCount) + if (inventory.Items.TryGetValue(i, out Item? curItem)) { - // Put item on that stack - int spaceLeft = maxCount - _item.Value.Count; - if (inventory.Items[slotId].Count <= spaceLeft) + if (TryMergeSlot(inventory, item!, slotId, curItem, i, changedSlots)) + break; + } + else if (firstEmptySlot == -1) + firstEmptySlot = i; + } + if (item!.Count > 0) + { + if (firstEmptySlot != -1) + StoreInNewSlot(inventory, item, slotId, firstEmptySlot, changedSlots); + else if (item.Count != itemCount) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + } + } + else if (upper2backpack) + { + int hotbarEnd = lowerStartSlot + 4 * 9 - 1; + if (hotbarFirst) + { + int lastEmptySlot = -1; + for (int i = hotbarEnd; i >= lowerStartSlot; --i) + { + if (inventory.Items.TryGetValue(i, out Item? curItem)) { - // 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])); + if (TryMergeSlot(inventory, item!, slotId, curItem, i, changedSlots)) + break; } + else if (lastEmptySlot == -1) + lastEmptySlot = i; + } + if (item!.Count > 0) + { + if (lastEmptySlot != -1) + StoreInNewSlot(inventory, item, slotId, lastEmptySlot, changedSlots); + else if (item.Count != itemCount) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } } - if (inventory.Items[slotId].Count > 0) + else { - int[] emptySlots = inventory.GetEmpytSlots(); - int emptySlot = -2; - foreach (int slot in emptySlots) + int firstEmptySlot = -1; + for (int i = lowerStartSlot; i <= hotbarEnd; ++i) { - if (slot <= upperEndSlot) continue; - emptySlot = slot; - break; + if (inventory.Items.TryGetValue(i, out Item? curItem)) + { + if (TryMergeSlot(inventory, item!, slotId, curItem, i, changedSlots)) + break; + } + else if (firstEmptySlot == -1) + firstEmptySlot = i; } - if (emptySlot != -2) + if (item!.Count > 0) { - 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])); + if (firstEmptySlot != -1) + StoreInNewSlot(inventory, item, slotId, firstEmptySlot, changedSlots); + else if (item.Count != itemCount) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } } } - else + else if (backpack2hotbar) { - // Clicked slot is on hotbar, put it to upper inventory - // Now try to find same item and put on them - var itemsClone = playerInventory.Items.ToDictionary(entry => entry.Key, entry => entry.Value); - foreach (KeyValuePair _item in itemsClone) + int hotbarEnd = lowerStartSlot + 1 * 9 - 1; + + int firstEmptySlot = -1; + for (int i = lowerStartSlot; i <= hotbarEnd; ++i) { - if (_item.Key < upperStartSlot) continue; - if (_item.Key >= upperEndSlot) break; - - int maxCount = _item.Value.Type.StackCount(); - if (_item.Value.Type == inventory.Items[slotId].Type && _item.Value.Count < maxCount) + if (inventory.Items.TryGetValue(i, out Item? curItem)) { - // Put item on that stack - int spaceLeft = maxCount - _item.Value.Count; - if (inventory.Items[slotId].Count <= spaceLeft) - { - // 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])); - } + if (TryMergeSlot(inventory, item!, slotId, curItem, i, changedSlots)) + break; } + else if (firstEmptySlot == -1) + firstEmptySlot = i; } - if (inventory.Items[slotId].Count > 0) + if (item!.Count > 0) { - int[] emptySlots = inventory.GetEmpytSlots(); - int emptySlot = -2; - foreach (int slot in emptySlots) - { - if (slot < upperStartSlot) continue; - if (slot >= upperEndSlot) break; - emptySlot = slot; - break; - } - if (emptySlot != -2) - { - 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])); - } + if (firstEmptySlot != -1) + StoreInNewSlot(inventory, item, slotId, firstEmptySlot, changedSlots); + else if (item.Count != itemCount) + changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); } } } @@ -1592,19 +2048,19 @@ namespace MinecraftClient if (inventory.Items.ContainsKey(slotId)) { inventory.Items[slotId].Count--; - changedSlots.Add(new Tuple((short)slotId, inventory.Items[slotId])); + 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, null)); } break; case WindowActionType.DropItemStack: inventory.Items.Remove(slotId); - changedSlots.Add(new Tuple((short)slotId, null)); + changedSlots.Add(new Tuple((short)slotId, null)); break; } } diff --git a/MinecraftClient/Protocol/Handlers/DataTypes.cs b/MinecraftClient/Protocol/Handlers/DataTypes.cs index f9a75213..d026c919 100644 --- a/MinecraftClient/Protocol/Handlers/DataTypes.cs +++ b/MinecraftClient/Protocol/Handlers/DataTypes.cs @@ -741,7 +741,7 @@ namespace MinecraftClient.Protocol.Handlers /// /// Dictionary to encode as Nbt /// Byte array for this NBT tag - public byte[] GetNbt(Dictionary nbt) + public byte[] GetNbt(Dictionary? nbt) { return GetNbt(nbt, true); } @@ -752,7 +752,7 @@ namespace MinecraftClient.Protocol.Handlers /// Dictionary to encode as Nbt /// TRUE if starting a new NBT tag, FALSE if processing a nested NBT tag /// Byte array for this NBT tag - private byte[] GetNbt(Dictionary nbt, bool root) + private byte[] GetNbt(Dictionary? nbt, bool root) { if (nbt == null || nbt.Count == 0) return new byte[] { 0 }; // TAG_End @@ -1065,9 +1065,9 @@ namespace MinecraftClient.Protocol.Handlers /// Item /// Item Palette /// Item slot representation - public byte[] GetItemSlot(Item item, ItemPalette itemPalette) + public byte[] GetItemSlot(Item? item, ItemPalette itemPalette) { - List slotData = new List(); + List slotData = new(); if (protocolversion > Protocol18Handler.MC_1_13_Version) { // MC 1.13 and greater diff --git a/MinecraftClient/Protocol/Handlers/Protocol16.cs b/MinecraftClient/Protocol/Handlers/Protocol16.cs index b0ff6546..4c8542e7 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol16.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol16.cs @@ -752,7 +752,7 @@ namespace MinecraftClient.Protocol.Handlers return false; //Currently not implemented } - public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List> changedSlots, 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 4e81ce3e..3f7b233c 100644 --- a/MinecraftClient/Protocol/Handlers/Protocol18.cs +++ b/MinecraftClient/Protocol/Handlers/Protocol18.cs @@ -2633,7 +2633,7 @@ namespace MinecraftClient.Protocol.Handlers catch (ObjectDisposedException) { return false; } } - public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List> changedSlots, int stateId) + public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item? item, List> changedSlots, int stateId) { try { @@ -2668,7 +2668,7 @@ namespace MinecraftClient.Protocol.Handlers case WindowActionType.AddDragMiddle: button = 9; mode = 5; item = new Item(ItemType.Null, 0, null); break; } - List packet = new List(); + List packet = new(); packet.Add((byte)windowId); // Window ID // 1.18+ diff --git a/MinecraftClient/Protocol/IMinecraftCom.cs b/MinecraftClient/Protocol/IMinecraftCom.cs index 52bfcac7..d584d13b 100644 --- a/MinecraftClient/Protocol/IMinecraftCom.cs +++ b/MinecraftClient/Protocol/IMinecraftCom.cs @@ -169,7 +169,7 @@ namespace MinecraftClient.Protocol /// 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, List> changedSlots, 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 4e0de511..5ef147dd 100644 --- a/MinecraftClient/Protocol/IMinecraftComHandler.cs +++ b/MinecraftClient/Protocol/IMinecraftComHandler.cs @@ -41,7 +41,7 @@ namespace MinecraftClient.Protocol bool GetNetworkPacketCaptureEnabled(); void SetNetworkPacketCaptureEnabled(bool enabled); int GetProtocolVersion(); - Container GetInventory(int inventoryID); + Container? GetInventory(int inventoryID); ILogger GetLogger(); /// diff --git a/MinecraftClient/Resources/lang/en.ini b/MinecraftClient/Resources/lang/en.ini index eae606f7..1d0cc4ad 100644 --- a/MinecraftClient/Resources/lang/en.ini +++ b/MinecraftClient/Resources/lang/en.ini @@ -296,6 +296,8 @@ cmd.inventory.left=Left cmd.inventory.right=Right cmd.inventory.middle=Middle cmd.inventory.clicking={0} clicking slot {1} in window #{2} +cmd.inventory.shiftclick=Shift clicking slot {0} in window #{1} +cmd.inventory.shiftclick_fail=Shift click failed, this may be because this container type is not supported cmd.inventory.no_item=No item in slot #{0} cmd.inventory.drop=Dropped 1 item from slot #{0} cmd.inventory.drop_stack=Dropped whole item stack from slot #{0} @@ -307,6 +309,7 @@ cmd.inventory.help.usage=Usage cmd.inventory.help.list=List your inventory. cmd.inventory.help.close=Close an opened container. cmd.inventory.help.click=Click on an item. +cmd.inventory.help.shiftclick=Shift click an item. cmd.inventory.help.drop=Drop an item from inventory. cmd.inventory.help.creativegive=Give item in creative mode. cmd.inventory.help.creativedelete=Clear slot in creative mode.