Support for shift-clicking in containers

Support for shift-clicking in containers
This commit is contained in:
BruceChen 2022-09-08 17:21:38 +08:00 committed by GitHub
commit 5181395bbd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
8 changed files with 763 additions and 302 deletions

View file

@ -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<string, object> localVars)
public override string Run(McClient handler, string command, Dictionary<string, object>? 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<int> 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<int, Item> itemsSorted = new SortedDictionary<int, Item>(inventory.Items);
List<string> response = new List<string>();
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<int, Item> 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<int> 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<int, Item>(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<int, Container> inventories = handler.GetInventories();
List<string> response = new List<string>();
response.Add(Translations.Get("cmd.inventory.inventories") + ":");
foreach (KeyValuePair<int, Container> 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 <player|container|<id>> <action>.";
}
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 <player|container|<id>> list";
case "close":
return Translations.Get("cmd.inventory.help.close") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory <player|container|<id>> close";
case "click":
return Translations.Get("cmd.inventory.help.click") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory <player|container|<id>> click <slot> [left|right|middle]. \nDefault is left click";
case "drop":
return Translations.Get("cmd.inventory.help.drop") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory <player|container|<id>> drop <slot> [all]. \nAll means drop full stack";
case "creativegive":
return Translations.Get("cmd.inventory.help.creativegive") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory creativegive <slot> <itemtype> <amount>";
case "creativedelete":
return Translations.Get("cmd.inventory.help.creativedelete") + ' ' + Translations.Get("cmd.inventory.help.usage") + ": /inventory creativedelete <slot>";
case "help":
return GetHelp();
default:
return Translations.Get("cmd.inventory.help.unknown") + GetAvailableActions();
}
"list" => Translations.Get("cmd.inventory.help.list") + usageStr + "/inventory <player|container|<id>> list",
"close" => Translations.Get("cmd.inventory.help.close") + usageStr + "/inventory <player|container|<id>> close",
"click" => Translations.Get("cmd.inventory.help.click") + usageStr + "/inventory <player|container|<id>> click <slot> [left|right|middle]\nDefault is left click",
"shiftclick" => Translations.Get("cmd.inventory.help.shiftclick") + usageStr + "/inventory <player|container|<id>> shiftclick <slot>",
"drop" => Translations.Get("cmd.inventory.help.drop") + usageStr + "/inventory <player|container|<id>> drop <slot> [all]\nAll means drop full stack",
"creativegive" => Translations.Get("cmd.inventory.help.creativegive") + usageStr + "/inventory creativegive <slot> <itemtype> <amount>",
"creativedelete" => Translations.Get("cmd.inventory.help.creativedelete") + usageStr + "/inventory creativedelete <slot>",
"help" => GetHelp(),
_ => Translations.Get("cmd.inventory.help.unknown") + GetAvailableActions(),
};
}
#endregion
}

View file

@ -1027,7 +1027,7 @@ namespace MinecraftClient
/// </summary>
/// <param name="inventoryID">Window ID of the requested inventory</param>
/// <returns> Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)</returns>
public Container GetInventory(int inventoryID)
public Container? GetInventory(int inventoryID)
{
if (InvokeRequired)
return InvokeOnMainThread(() => GetInventory(inventoryID));
@ -1043,7 +1043,7 @@ namespace MinecraftClient
/// <returns> Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)</returns>
public Container GetPlayerInventory()
{
return GetInventory(0);
return GetInventory(0)!;
}
/// <summary>
@ -1277,6 +1277,63 @@ namespace MinecraftClient
return InvokeOnMainThread(() => handler.SendUseItem(0, this.sequenceId));
}
/// <summary>
/// Try to merge a slot
/// </summary>
/// <param name="inventory">The container where the item is located</param>
/// <param name="item">Items to be processed</param>
/// <param name="slotId">The ID of the slot of the item to be processed</param>
/// <param name="curItem">The slot that was put down</param>
/// <param name="curId">The ID of the slot being put down</param>
/// <param name="changedSlots">Record changes</param>
/// <returns>Whether to fully merge</returns>
private static bool TryMergeSlot(Container inventory, Item item, int slotId, Item curItem, int curId, List<Tuple<short, Item?>> 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, Item?>((short)curId, curItem));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, null));
inventory.Items.Remove(slotId);
return true;
}
else
{
item.Count -= spaceLeft;
curItem.Count += spaceLeft;
changedSlots.Add(new Tuple<short, Item?>((short)curId, curItem));
}
}
return false;
}
/// <summary>
/// Store items in a new slot
/// </summary>
/// <param name="inventory">The container where the item is located</param>
/// <param name="item">Items to be processed</param>
/// <param name="slotId">The ID of the slot of the item to be processed</param>
/// <param name="newSlotId">ID of the new slot</param>
/// <param name="changedSlots">Record changes</param>
private static void StoreInNewSlot(Container inventory, Item item, int slotId, int newSlotId, List<Tuple<short, Item?>> changedSlots)
{
Item newItem = new(item.Type, item.Count, item.NBT);
inventory.Items[newSlotId] = newItem;
inventory.Items.Remove(slotId);
changedSlots.Add(new Tuple<short, Item?>((short)newSlotId, newItem));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, null));
}
/// <summary>
/// Click a slot in the specified window
/// </summary>
@ -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<Tuple<short, Item>> changedSlots = new List<Tuple<short, Item>>(); // List<Slot ID, Changed Items>
List<Tuple<short, Item?>> changedSlots = new(); // List<Slot ID, Changed Items>
// 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, Item>((short)slotId, inventory.Items[slotId]));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, inventory.Items[slotId]));
else
changedSlots.Add(new Tuple<short, Item>((short)slotId, null));
changedSlots.Add(new Tuple<short, Item?>((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, Item>((short)slotId, null));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, null));
}
}
break;
@ -1439,30 +1496,455 @@ namespace MinecraftClient
}
}
if (inventory.Items.ContainsKey(slotId))
changedSlots.Add(new Tuple<short, Item>((short)slotId, inventory.Items[slotId]));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, inventory.Items[slotId]));
else
changedSlots.Add(new Tuple<short, Item>((short)slotId, null));
changedSlots.Add(new Tuple<short, Item?>((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<int, Item> _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, Item?>((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>((short)_item.Key, inventory.Items[_item.Key]));
changedSlots.Add(new Tuple<short, Item>((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>((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, Item?>((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, Item>((short)emptySlot, inventory.Items[emptySlot]));
changedSlots.Add(new Tuple<short, Item>((short)slotId, null));
}
else if (inventory.Items[slotId].Count != itemCount)
{
changedSlots.Add(new Tuple<short, Item>((short)slotId, inventory.Items[slotId]));
if (firstEmptySlot != -1)
StoreInNewSlot(inventory, item, slotId, firstEmptySlot, changedSlots);
else if (item.Count != itemCount)
changedSlots.Add(new Tuple<short, Item?>((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<int, Item> _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>((short)_item.Key, inventory.Items[_item.Key]));
changedSlots.Add(new Tuple<short, Item>((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>((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, Item>((short)emptySlot, inventory.Items[emptySlot]));
changedSlots.Add(new Tuple<short, Item>((short)slotId, null));
}
else if (inventory.Items[slotId].Count != itemCount)
{
changedSlots.Add(new Tuple<short, Item>((short)slotId, inventory.Items[slotId]));
}
if (firstEmptySlot != -1)
StoreInNewSlot(inventory, item, slotId, firstEmptySlot, changedSlots);
else if (item.Count != itemCount)
changedSlots.Add(new Tuple<short, Item?>((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, Item>((short)slotId, inventory.Items[slotId]));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, inventory.Items[slotId]));
}
if (inventory.Items[slotId].Count <= 0)
{
inventory.Items.Remove(slotId);
changedSlots.Add(new Tuple<short, Item>((short)slotId, null));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, null));
}
break;
case WindowActionType.DropItemStack:
inventory.Items.Remove(slotId);
changedSlots.Add(new Tuple<short, Item>((short)slotId, null));
changedSlots.Add(new Tuple<short, Item?>((short)slotId, null));
break;
}
}

View file

@ -741,7 +741,7 @@ namespace MinecraftClient.Protocol.Handlers
/// </summary>
/// <param name="nbt">Dictionary to encode as Nbt</param>
/// <returns>Byte array for this NBT tag</returns>
public byte[] GetNbt(Dictionary<string, object> nbt)
public byte[] GetNbt(Dictionary<string, object>? nbt)
{
return GetNbt(nbt, true);
}
@ -752,7 +752,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="nbt">Dictionary to encode as Nbt</param>
/// <param name="root">TRUE if starting a new NBT tag, FALSE if processing a nested NBT tag</param>
/// <returns>Byte array for this NBT tag</returns>
private byte[] GetNbt(Dictionary<string, object> nbt, bool root)
private byte[] GetNbt(Dictionary<string, object>? nbt, bool root)
{
if (nbt == null || nbt.Count == 0)
return new byte[] { 0 }; // TAG_End
@ -1065,9 +1065,9 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="item">Item</param>
/// <param name="itemPalette">Item Palette</param>
/// <returns>Item slot representation</returns>
public byte[] GetItemSlot(Item item, ItemPalette itemPalette)
public byte[] GetItemSlot(Item? item, ItemPalette itemPalette)
{
List<byte> slotData = new List<byte>();
List<byte> slotData = new();
if (protocolversion > Protocol18Handler.MC_1_13_Version)
{
// MC 1.13 and greater

View file

@ -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<Tuple<short, Item>> changedSlots, int stateId)
public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item? item, List<Tuple<short, Item?>> changedSlots, int stateId)
{
return false; //Currently not implemented
}

View file

@ -2633,7 +2633,7 @@ namespace MinecraftClient.Protocol.Handlers
catch (ObjectDisposedException) { return false; }
}
public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List<Tuple<short, Item>> changedSlots, int stateId)
public bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item? item, List<Tuple<short, Item?>> 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<byte> packet = new List<byte>();
List<byte> packet = new();
packet.Add((byte)windowId); // Window ID
// 1.18+

View file

@ -169,7 +169,7 @@ namespace MinecraftClient.Protocol
/// <param name="changedSlots">Slots that have been changed in this event: List<SlotID, Changed Items> </param>
/// <param name="stateId">Inventory's stateId</param>
/// <returns>True if packet was successfully sent</returns>
bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item item, List<Tuple<short, Item>> changedSlots, int stateId);
bool SendWindowAction(int windowId, int slotId, WindowActionType action, Item? item, List<Tuple<short, Item?>> changedSlots, int stateId);
/// <summary>
/// Request Creative Mode item creation into regular/survival Player Inventory

View file

@ -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();
/// <summary>

View file

@ -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.