Interact with inventories

The /inventory command allow listing inventory and clicking on items
Should be enough for operating GUI menus such as Server chooser/teleporter
This commit is contained in:
ORelio 2020-03-29 18:41:26 +02:00
parent bc3d6aba00
commit e04f06cece
13 changed files with 574 additions and 217 deletions

Binary file not shown.

View file

@ -149,6 +149,22 @@ namespace MinecraftClient.Protocol.Handlers
g = readNextBool(ref packetData);
Console.WriteLine("[C -> S] Location: " + x + ", " + y + ", " + z + ", (look)" + ", " + g);
break;
case 0x09:
byte window = readNextByte(ref packetData);
short slot = readNextShort(ref packetData);
byte button = readNextByte(ref packetData);
short action = readNextShort(ref packetData);
int mode = readNextVarInt(ref packetData);
bool slotPresent = readNextBool(ref packetData);
int itemId = -1;
byte itemCount = 0;
if (slotPresent)
{
itemId = readNextVarInt(ref packetData);
itemCount = readNextByte(ref packetData);
}
Console.WriteLine("[C -> S] Window #" + window + " click: #" + slot + " button " + button + " action " + action + " mode " + mode + " item " + itemId + " x" + itemCount);
break;
}
}
}
@ -209,6 +225,19 @@ namespace MinecraftClient.Protocol.Handlers
return rawValue[0] != 0;
}
private byte readNextByte(ref byte[] cache)
{
byte[] rawValue = readData(1, ref cache);
return rawValue[0];
}
private short readNextShort(ref byte[] cache)
{
byte[] rawValue = readData(2, ref cache);
Array.Reverse(rawValue); //Endianness
return BitConverter.ToInt16(rawValue, 0);
}
private double readNextDouble(ref byte[] cache)
{
byte[] rawValue = readData(8, ref cache);

View file

@ -6,23 +6,63 @@ using MinecraftClient.Inventory;
namespace MinecraftClient.Commands
{
class GetInventory : Command
class Inventory : Command
{
public override string CMDName { get { return "inventory"; } }
public override string CMDDesc { get { return "inventory: Show your inventory."; } }
public override string CMDDesc { get { return "inventory <id> <list|close|click <slot>>: Interact with inventories"; } }
public override string Run(McTcpClient handler, string command, Dictionary<string, object> localVars)
{
List<string> response = new List<string>();
response.Add("Inventory slots:");
foreach (KeyValuePair<int, Item> item in handler.GetPlayerInventory().Items)
if (handler.GetInventoryEnabled())
{
response.Add(String.Format(" #{0}: {1} x{2}", item.Key, item.Value.Type, item.Value.Count));
string[] args = getArgs(command);
if (args.Length >= 1)
{
try
{
int inventoryId = int.Parse(args[0]);
string action = args.Length > 1
? args[1].ToLower()
: "list";
switch (action)
{
case "close":
if (handler.CloseInventory(inventoryId))
return "Closing Inventoy #" + inventoryId;
else return "Failed to close Inventory #" + inventoryId;
case "list":
Container inventory = handler.GetInventory(inventoryId);
List<string> response = new List<string>();
response.Add("Inventory #" + inventoryId + " - " + inventory.Title + "§8");
foreach (KeyValuePair<int, Item> item in inventory.Items)
response.Add(String.Format(" #{0}: {1} x{2}", item.Key, item.Value.Type, item.Value.Count));
return String.Join("\n", response.ToArray());
case "click":
if (args.Length == 3)
{
int slot = int.Parse(args[2]);
handler.ClickWindowSlot(inventoryId, slot);
return "Clicking slot " + slot + " in window #" + inventoryId;
}
else return CMDDesc;
default:
return CMDDesc;
}
}
catch (FormatException) { return CMDDesc; }
}
else
{
Dictionary<int, Container> inventories = handler.GetInventories();
List<string> response = new List<string>();
response.Add("Inventories:");
foreach (var inventory in inventories)
response.Add(String.Format(" #{0}: {1}", inventory.Key, inventory.Value.Title + "§8"));
response.Add(CMDDesc);
return String.Join("\n", response);
}
}
return String.Join("\n", response.ToArray());
else return "Please enable inventoryhandling in config to use this command.";
}
}
}

View file

@ -12,8 +12,12 @@ namespace MinecraftClient.Commands
public override string Run(McTcpClient handler, string command, Dictionary<string, object> localVars)
{
handler.UseItemOnHand();
return "Use an item";
if (handler.GetInventoryEnabled())
{
handler.UseItemOnHand();
return "Used an item";
}
else return "Please enable inventoryhandling in config to use this command.";
}
}
}

View file

@ -20,44 +20,34 @@ namespace MinecraftClient.Inventory
/// </summary>
public int Count;
/// <summary>
/// Slot ID in the parent inventory
/// </summary>
/// <remarks>-1 means currently being dragged by mouse</remarks>
public int SlotID;
/// <summary>
/// Item Metadata
/// </summary>
public Dictionary<string, object> NBT;
/// <summary>
/// Create an item with Type ID, Count and Slot ID
/// Create an item with Type ID, Count and Metadata
/// </summary>
/// <param name="ID">Item Type ID</param>
/// <param name="Count">Item Count</param>
/// <param name="SlotID">Item Slot ID in parent inventory</param>
public Item(int id, int count, int slotID)
/// <param name="NBT">Item Metadata</param>
public Item(int id, int count, Dictionary<string, object> nbt)
{
this.Type = (ItemType)id;
this.Count = count;
this.SlotID = slotID;
this.NBT = new Dictionary<string, object>();
this.NBT = nbt;
}
/// <summary>
/// Create an item with Type ID, Count, Slot ID and Metadata
/// Check if the item slot is empty
/// </summary>
/// <param name="ID">Item Type ID</param>
/// <param name="Count">Item Count</param>
/// <param name="SlotID">Item Slot ID in parent inventory</param>
/// <param name="NBT">Item Metadata</param>
public Item(int id, int count, int slotID, Dictionary<string, object> nbt)
/// <returns>TRUE if the item is empty</returns>
public bool IsEmpty
{
this.Type = (ItemType)id;
this.Count = count;
this.SlotID = slotID;
this.NBT = nbt;
get
{
return Type == ItemType.Air || Count == 0;
}
}
}
}

View file

@ -27,7 +27,7 @@ namespace MinecraftClient
private readonly List<ChatBot> bots = new List<ChatBot>();
private static readonly List<ChatBot> botsOnHold = new List<ChatBot>();
private static List<Container> inventories = new List<Container>();
private static Dictionary<int, Container> inventories = new Dictionary<int, Container>();
private readonly Dictionary<string, List<ChatBot>> registeredBotPluginChannels = new Dictionary<string, List<ChatBot>>();
private readonly List<string> registeredServerPluginChannels = new List<String>();
@ -53,7 +53,6 @@ namespace MinecraftClient
private string username;
private string uuid;
private string sessionid;
private Container playerInventory = new Container(ContainerType.PlayerInventory);
private DateTime lastKeepAlive;
private object lastKeepAliveLock = new object();
@ -136,6 +135,12 @@ namespace MinecraftClient
inventoryHandlingEnabled = Settings.InventoryHandling;
entityHandlingEnabled = Settings.EntityHandling;
if (inventoryHandlingEnabled)
{
inventories.Clear();
inventories[0] = new Container(0, ContainerType.PlayerInventory, "Player Inventory");
}
bool retry = false;
this.sessionid = sessionID;
this.uuid = uuid;
@ -550,7 +555,6 @@ namespace MinecraftClient
inventoryHandlingEnabled = false;
inventoryHandlingRequested = false;
inventories.Clear();
playerInventory = null;
}
return true;
}
@ -592,13 +596,34 @@ namespace MinecraftClient
}
}
/// <summary>
/// Get all inventories. ID 0 is the player inventory.
/// </summary>
/// <returns>All inventories</returns>
public Dictionary<int, Container> GetInventories()
{
return inventories;
}
/// <summary>
/// Get client player's inventory items
/// </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)
{
if (inventories.ContainsKey(inventoryID))
return inventories[inventoryID];
return null;
}
/// <summary>
/// Get client player's inventory items
/// </summary>
/// <returns> Item Dictionary indexed by Slot ID (Check wiki.vg for slot ID)</returns>
public Container GetPlayerInventory()
{
return playerInventory;
return GetInventory(0);
}
/// <summary>
@ -765,19 +790,14 @@ namespace MinecraftClient
/// When an inventory is opened
/// </summary>
/// <param name="inventory">Location to reach</param>
public void OnInventoryOpen(Container inventory)
public void OnInventoryOpen(int inventoryID, Container inventory)
{
//TODO: Handle Inventory
if (!inventories.Contains(inventory))
{
inventories.Add(inventory);
}
inventories[inventoryID] = inventory;
if (Settings.DebugMessages)
if (inventoryID != 0)
{
ConsoleIO.WriteLineFormatted("§8An Inventory opened: " + inventory.Type + " - " + inventory.Title);
foreach (var item in inventory.Items)
ConsoleIO.WriteLineFormatted("§8 - Slot " + item.Key + ": " + item.Value.Type + " x" + item.Value.Count);
ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " opened: " + inventory.Title);
ConsoleIO.WriteLogLine("Use /inventory to interact with it.");
}
}
@ -785,64 +805,42 @@ namespace MinecraftClient
/// When an inventory is close
/// </summary>
/// <param name="inventoryID">Location to reach</param>
public void OnInventoryClose(byte inventoryID)
public void OnInventoryClose(int inventoryID)
{
for (int i = 0; i < inventories.Count; i++)
{
Container inventory = inventories[i];
if (inventory == null) continue;
if (inventory.Type == Container.GetContainerType(inventoryID))
{
inventories.Remove(inventory);
return;
}
}
if (inventories.ContainsKey(inventoryID))
inventories.Remove(inventoryID);
if (inventoryID != 0)
ConsoleIO.WriteLogLine("Inventory # " + inventoryID + " closed.");
}
/// <summary>
/// When received window items from server.
/// </summary>
/// <param name="type">Inventory type</param>
/// <param name="inventoryID">Inventory ID</param>
/// <param name="itemList">Item list, key = slot ID, value = Item information</param>
public void OnWindowItems(int type, Dictionary<int, Inventory.Item> itemList)
public void OnWindowItems(byte inventoryID, Dictionary<int, Inventory.Item> itemList)
{
// 0 is player inventory
if (type == 0)
playerInventory.Items = itemList;
if (Settings.DebugMessages)
{
ConsoleIO.WriteLineFormatted("§8Received Window of type " + type);
foreach (var item in itemList)
ConsoleIO.WriteLineFormatted("§8 - Slot " + item.Key + ": " + item.Value.Type + " x" + item.Value.Count);
}
}
/// <summary>
/// When a slot is cleared inside window items
/// </summary>
/// <param name="WindowID">Window ID</param>
/// <param name="SlotID">Slot ID</param>
public void OnSlotClear(byte WindowID, short SlotID)
{
if (WindowID == 0 && playerInventory.Items.ContainsKey(SlotID))
{
playerInventory.Items.Remove(SlotID);
}
if (inventories.ContainsKey(inventoryID))
inventories[inventoryID].Items = itemList;
}
/// <summary>
/// When a slot is set inside window items
/// </summary>
/// <param name="WindowID">Window ID</param>
/// <param name="SlotID">Slot ID</param>
/// <param name="ItemID">Item ID</param>
/// <param name="Count">Item Count</param>
/// <param name="NBT">Item Metadata</param>
public void OnSetSlot(byte WindowID, short SlotID, int ItemID, byte Count, Dictionary<string, object> NBT)
/// <param name="inventoryID">Window ID</param>
/// <param name="slotID">Slot ID</param>
/// <param name="item">Item (may be null for empty slot)</param>
public void OnSetSlot(byte inventoryID, short slotID, Item item)
{
if (WindowID == 0)
if (inventories.ContainsKey(inventoryID))
{
playerInventory.Items[SlotID] = new Inventory.Item(ItemID, Count, SlotID, NBT);
if (item == null || item.IsEmpty)
{
if (inventories[inventoryID].Items.ContainsKey(slotID))
inventories[inventoryID].Items.Remove(slotID);
}
else inventories[inventoryID].Items[slotID] = item;
}
}
@ -1312,7 +1310,35 @@ namespace MinecraftClient
/// <returns>TRUE if the item was successfully used</returns>
public bool UseItemOnHand()
{
return handler.SendUseItemPacket(0);
return handler.SendUseItem(0);
}
/// <summary>
/// Click a slot in the specified window
/// </summary>
/// <returns>TRUE if the slot was successfully clicked</returns>
public bool ClickWindowSlot(int windowId, int slotId)
{
Item item = null;
if (inventories.ContainsKey(windowId) && inventories[windowId].Items.ContainsKey(slotId))
item = inventories[windowId].Items[slotId];
return handler.SendClickWindow(windowId, slotId, item);
}
/// <summary>
/// Close the specified inventory window
/// </summary>
/// <param name="windowId">Window ID</param>
/// <returns>TRUE if the window was successfully closed</returns>
public bool CloseInventory(int windowId)
{
if (windowId != 0 && inventories.ContainsKey(windowId))
{
inventories.Remove(windowId);
return handler.SendCloseWindow(windowId);
}
return false;
}
/// <summary>
@ -1320,10 +1346,10 @@ namespace MinecraftClient
/// </summary>
/// <param name="EntityID"></param>
/// <param name="type">0: interact, 1: attack, 2: interact at</param>
/// <returns></returns>
/// <returns>TRUE if interaction succeeded</returns>
public bool InteractEntity(int EntityID, int type)
{
return handler.SendInteractEntityPacket(EntityID, type);
return handler.SendInteractEntity(EntityID, type);
}
/// <summary>
@ -1342,7 +1368,7 @@ namespace MinecraftClient
/// Change active slot in the player inventory
/// </summary>
/// <param name="slot">Slot to activate (0 to 8)</param>
/// <returns></returns>
/// <returns>TRUE if the slot was changed</returns>
public bool ChangeSlot(short slot)
{
if (slot >= 0 && slot <= 8)

View file

@ -5,6 +5,7 @@ using System.Text;
using System.Net.Sockets;
using MinecraftClient.Mapping;
using MinecraftClient.Crypto;
using MinecraftClient.Inventory;
namespace MinecraftClient.Protocol.Handlers
{
@ -319,6 +320,40 @@ namespace MinecraftClient.Protocol.Handlers
return ReadNextNbt(cache, true);
}
/// <summary>
/// Read a single item slot from a cache of byte and remove it from the cache
/// </summary>
/// <param name="item">Item</param>
/// <returns>The item that was read or NULL for an empty slot</returns>
public Item ReadNextItemSlot(List<byte> cache)
{
List<byte> slotData = new List<byte>();
if (protocolversion > Protocol18Handler.MC113Version)
{
// MC 1.13 and greater
bool itemPresent = ReadNextBool(cache);
if (itemPresent)
{
int itemID = ReadNextVarInt(cache);
byte itemCount = ReadNextByte(cache);
Dictionary<string, object> NBT = ReadNextNbt(cache);
return new Item(itemID, itemCount, NBT);
}
else return null;
}
else
{
// MC 1.12.2 and lower
short itemID = ReadNextShort(cache);
if (itemID == -1)
return null;
byte itemCount = ReadNextByte(cache);
short itemDamage = ReadNextShort(cache);
Dictionary<string, object> NBT = ReadNextNbt(cache);
return new Item(itemID, itemCount, NBT);
}
}
/// <summary>
/// Read an uncompressed Named Binary Tag blob and remove it from the cache (internal)
/// </summary>
@ -395,6 +430,163 @@ namespace MinecraftClient.Protocol.Handlers
}
}
/// <summary>
/// Build an uncompressed Named Binary Tag blob for sending over the network
/// </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)
{
return GetNbt(nbt, true);
}
/// <summary>
/// Build an uncompressed Named Binary Tag blob for sending over the network (internal)
/// </summary>
/// <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)
{
if (nbt == null || nbt.Count == 0)
return new byte[] { 0 }; // TAG_End
List<byte> bytes = new List<byte>();
if (root)
{
bytes.Add(10); // TAG_Compound
}
foreach (var item in nbt)
{
byte fieldType;
byte[] fieldNameLength = GetUShort((ushort)item.Key.Length);
byte[] fieldName = Encoding.ASCII.GetBytes(item.Key);
byte[] fieldData = GetNbtField(item.Value, out fieldType);
bytes.AddRange(fieldNameLength);
bytes.AddRange(fieldName);
bytes.Add(fieldType);
bytes.AddRange(fieldData);
}
bytes.Add(0); // TAG_End
return bytes.ToArray();
}
/// <summary>
/// Convert a single object into its NBT representation (internal)
/// </summary>
/// <param name="obj">Object to convert</param>
/// <param name="fieldType">Field type for the passed object</param>
/// <returns>Binary data for the passed object</returns>
private byte[] GetNbtField(object obj, out byte fieldType)
{
if (obj is byte)
{
fieldType = 1; // TAG_Byte
return new[] { (byte)obj };
}
else if (obj is short)
{
fieldType = 2; // TAG_Short
return GetShort((short)obj);
}
else if (obj is int)
{
fieldType = 3; // TAG_Int
return GetInt((int)obj);
}
else if (obj is long)
{
fieldType = 4; // TAG_Long
return GetLong((long)obj);
}
else if (obj is float)
{
fieldType = 5; // TAG_Float
return GetFloat((float)obj);
}
else if (obj is double)
{
fieldType = 6; // TAG_Double
return GetDouble((double)obj);
}
else if (obj is byte[])
{
fieldType = 7; // TAG_Byte_Array
return (byte[])obj;
}
else if (obj is string)
{
fieldType = 8; // TAG_String
byte[] stringBytes = Encoding.UTF8.GetBytes((string)obj);
return ConcatBytes(GetUShort((ushort)stringBytes.Length), stringBytes);
}
else if (obj is object[])
{
fieldType = 9; // TAG_List
List<object> list = new List<object>((object[])obj);
int arrayLengthTotal = list.Count;
// Treat empty list as TAG_Byte, length 0
if (arrayLengthTotal == 0)
return ConcatBytes(new[] { (byte)1 }, GetInt(0));
// Encode first list item, retain its type
byte firstItemType;
string firstItemTypeString = list[0].GetType().Name;
byte[] firstItemBytes = GetNbtField(list[0], out firstItemType);
list.RemoveAt(0);
// Encode further list items, check they have the same type
byte subsequentItemType;
List<byte> subsequentItemsBytes = new List<byte>();
foreach (object item in list)
{
subsequentItemsBytes.AddRange(GetNbtField(item, out subsequentItemType));
if (subsequentItemType != firstItemType)
throw new System.IO.InvalidDataException(
"GetNbt: Cannot encode object[] list with mixed types: " + firstItemTypeString + ", " + item.GetType().Name + " into NBT!");
}
// Build NBT list: type, length, item array
return ConcatBytes(new[] { firstItemType }, GetInt(arrayLengthTotal), firstItemBytes, subsequentItemsBytes.ToArray());
}
else if (obj is Dictionary<string, object>)
{
fieldType = 10; // TAG_Compound
return GetNbt((Dictionary<string, object>)obj, false);
}
else if (obj is int[])
{
fieldType = 11; // TAG_Int_Array
int[] srcIntList = (int[])obj;
List<byte> encIntList = new List<byte>();
encIntList.AddRange(GetInt(srcIntList.Length));
foreach (int item in srcIntList)
encIntList.AddRange(GetInt(item));
return encIntList.ToArray();
}
else if (obj is long[])
{
fieldType = 12; // TAG_Long_Array
long[] srcLongList = (long[])obj;
List<byte> encLongList = new List<byte>();
encLongList.AddRange(GetInt(srcLongList.Length));
foreach (long item in srcLongList)
encLongList.AddRange(GetLong(item));
return encLongList.ToArray();
}
else
{
throw new System.IO.InvalidDataException("GetNbt: Cannot encode data type " + obj.GetType().Name + " into NBT!");
}
}
/// <summary>
/// Build an integer for sending over the network
/// </summary>
@ -412,6 +604,30 @@ namespace MinecraftClient.Protocol.Handlers
return bytes.ToArray();
}
/// <summary>
/// Get byte array representing a long integer
/// </summary>
/// <param name="number">Long to process</param>
/// <returns>Array ready to send</returns>
public byte[] GetLong(long number)
{
byte[] theLong = BitConverter.GetBytes(number);
Array.Reverse(theLong);
return theLong;
}
/// <summary>
/// Get byte array representing an integer
/// </summary>
/// <param name="number">Integer to process</param>
/// <returns>Array ready to send</returns>
public byte[] GetInt(int number)
{
byte[] theInt = BitConverter.GetBytes(number);
Array.Reverse(theInt);
return theInt;
}
/// <summary>
/// Get byte array representing a short
/// </summary>
@ -424,6 +640,18 @@ namespace MinecraftClient.Protocol.Handlers
return theShort;
}
/// <summary>
/// Get byte array representing an unsigned short
/// </summary>
/// <param name="number">Short to process</param>
/// <returns>Array ready to send</returns>
public byte[] GetUShort(ushort number)
{
byte[] theShort = BitConverter.GetBytes(number);
Array.Reverse(theShort);
return theShort;
}
/// <summary>
/// Get byte array representing a double
/// </summary>
@ -448,7 +676,6 @@ namespace MinecraftClient.Protocol.Handlers
return theFloat;
}
/// <summary>
/// Get byte array with length information prepended to it
/// </summary>
@ -473,7 +700,6 @@ namespace MinecraftClient.Protocol.Handlers
public byte[] GetString(string text)
{
byte[] bytes = Encoding.UTF8.GetBytes(text);
return ConcatBytes(GetVarInt(bytes.Length), bytes);
}
@ -499,6 +725,42 @@ namespace MinecraftClient.Protocol.Handlers
return locationBytes;
}
/// <summary>
/// Get a byte array representing the given item as an item slot
/// </summary>
/// <param name="item">Item</param>
/// <returns>Item slot representation</returns>
public byte[] GetItemSlot(Item item)
{
List<byte> slotData = new List<byte>();
if (protocolversion > Protocol18Handler.MC113Version)
{
// MC 1.13 and greater
if (item == null || item.IsEmpty)
slotData.Add(0); // No item
else
{
slotData.Add(1); // Item is present
slotData.AddRange(GetVarInt((int)item.Type));
slotData.Add((byte)item.Count);
slotData.AddRange(GetNbt(item.NBT));
}
}
else
{
// MC 1.12.2 and lower
if (item == null || item.IsEmpty)
slotData.AddRange(GetShort(-1));
else
{
slotData.AddRange(GetShort((short)item.Type));
slotData.Add((byte)item.Count);
slotData.AddRange(GetNbt(item.NBT));
}
}
return slotData.ToArray();
}
/// <summary>
/// Easily append several byte arrays
/// </summary>

View file

@ -23,6 +23,8 @@ namespace MinecraftClient.Protocol.Handlers
HeldItemChange,
InteractEntity,
UseItem,
ClickWindow,
CloseWindow,
PlayerBlockPlacement
}
}

View file

@ -8,6 +8,7 @@ using MinecraftClient.Crypto;
using MinecraftClient.Proxy;
using System.Security.Cryptography;
using MinecraftClient.Mapping;
using MinecraftClient.Inventory;
namespace MinecraftClient.Protocol.Handlers
{
@ -641,22 +642,32 @@ namespace MinecraftClient.Protocol.Handlers
return false; //Currently not implemented
}
public bool SendInteractEntityPacket(int EntityID, int type)
public bool SendInteractEntity(int EntityID, int type)
{
return false; //Currently not implemented
}
public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z, int hand)
public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand)
{
return false; //Currently not implemented
}
public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z)
public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z)
{
return false; //Currently not implemented
}
public bool SendUseItemPacket(int hand)
public bool SendUseItem(int hand)
{
return false; //Currently not implemented
}
public bool SendClickWindow(int windowId, int slotId, Item item)
{
return false; //Currently not implemented
}
public bool SendCloseWindow(int windowId)
{
return false; //Currently not implemented
}

View file

@ -41,6 +41,7 @@ namespace MinecraftClient.Protocol.Handlers
private bool autocomplete_received = false;
private int autocomplete_transaction_id = 0;
private readonly List<string> autocomplete_result = new List<string>();
private readonly Dictionary<int, short> window_actions = new Dictionary<int, short>();
private bool login_phase = true;
private int protocolversion;
private int currentDimension;
@ -497,23 +498,27 @@ namespace MinecraftClient.Protocol.Handlers
case PacketIncomingType.OpenWindow:
if (handler.GetInventoryEnabled())
{
if (protocolversion < MC114Version) // packet changed at 1.14
if (protocolversion < MC114Version)
{
// MC 1.13 or lower
byte windowID = dataTypes.ReadNextByte(packetData);
string type = dataTypes.ReadNextString(packetData).Replace("minecraft:", "").ToUpper();
ContainerTypeOld inventoryType = (ContainerTypeOld)Enum.Parse(typeof(ContainerTypeOld), type);
string title = dataTypes.ReadNextString(packetData);
byte slots = dataTypes.ReadNextByte(packetData);
Container inventory = new Container(windowID, inventoryType, title);
handler.OnInventoryOpen(inventory);
Container inventory = new Container(windowID, inventoryType, ChatParser.ParseText(title));
window_actions[windowID] = 0;
handler.OnInventoryOpen(windowID, inventory);
}
else
{
int WindowID = dataTypes.ReadNextVarInt(packetData);
int WindowType = dataTypes.ReadNextVarInt(packetData);
// MC 1.14 or greater
int windowID = dataTypes.ReadNextVarInt(packetData);
int windowType = dataTypes.ReadNextVarInt(packetData);
string title = dataTypes.ReadNextString(packetData);
Container inventory = new Container(WindowID, WindowType, title);
handler.OnInventoryOpen(inventory);
Container inventory = new Container(windowID, windowType, ChatParser.ParseText(title));
window_actions[windowID] = 0;
handler.OnInventoryOpen(windowID, inventory);
}
}
break;
@ -521,103 +526,32 @@ namespace MinecraftClient.Protocol.Handlers
if (handler.GetInventoryEnabled())
{
byte windowID = dataTypes.ReadNextByte(packetData);
window_actions[windowID] = 0;
handler.OnInventoryClose(windowID);
}
break;
case PacketIncomingType.WindowItems:
if (handler.GetInventoryEnabled())
{
// MC 1.12.2 or lower
if (protocolversion < MC113Version)
byte windowId = dataTypes.ReadNextByte(packetData);
short elements = dataTypes.ReadNextShort(packetData);
Dictionary<int, Item> inventorySlots = new Dictionary<int, Item>();
for (short slotId = 0; slotId < elements; slotId++)
{
byte id = dataTypes.ReadNextByte(packetData);
short elements = dataTypes.ReadNextShort(packetData);
Dictionary<int, Item> itemsList = new Dictionary<int, Item>(); // index is SlotID
for (int i = 0; i < elements; i++)
{
short itemID = dataTypes.ReadNextShort(packetData);
if (itemID == -1) continue;
byte itemCount = dataTypes.ReadNextByte(packetData);
short itemDamage = dataTypes.ReadNextShort(packetData);
Dictionary<string, object> NBT = new Dictionary<string, object>();
//TODO: Add to the dictionary for the inventory its in using the id
if (packetData.ToArray().Count() > 0)
{
NBT = dataTypes.ReadNextNbt(packetData);
}
Item item = new Item(itemID, itemCount, itemDamage, NBT);
itemsList.Add(i, item);
}
handler.OnWindowItems(id, itemsList);
}
else
{
// MC 1.13 after
byte id = dataTypes.ReadNextByte(packetData);
short elements = dataTypes.ReadNextShort(packetData);
Dictionary<int, Item> itemsList = new Dictionary<int, Item>(); // index is SlotID
for (int i = 0; i < elements; i++)
{
bool haveItem = dataTypes.ReadNextBool(packetData);
if (haveItem)
{
int itemID = dataTypes.ReadNextVarInt(packetData);
byte itemCount = dataTypes.ReadNextByte(packetData);
dataTypes.ReadNextNbt(packetData);
Item item = new Item(itemID, itemCount, i);
itemsList.Add(i, item);
}
}
handler.OnWindowItems(id, itemsList);
Item item = dataTypes.ReadNextItemSlot(packetData);
if (item != null)
inventorySlots[slotId] = item;
}
handler.OnWindowItems(windowId, inventorySlots);
}
break;
case PacketIncomingType.SetSlot:
if(handler.GetInventoryEnabled())
if (handler.GetInventoryEnabled())
{
// MC 1.12.2 or lower
if (protocolversion < MC113Version)
{
byte WindowID = dataTypes.ReadNextByte(packetData);
short SlotID = dataTypes.ReadNextShort(packetData);
short ItemID = dataTypes.ReadNextShort(packetData);
if (ItemID == -1)
{
handler.OnSlotClear(WindowID, SlotID);
}
else
{
byte Count = dataTypes.ReadNextByte(packetData);
short itemDamage = dataTypes.ReadNextShort(packetData); // useless so ignored
Dictionary<string, object> NBT = new Dictionary<string, object>();
//TODO: Add to the dictionary for the inventory its in using the id
if (packetData.ToArray().Count() > 0)
{
NBT = dataTypes.ReadNextNbt(packetData);
}
handler.OnSetSlot(WindowID, SlotID, ItemID, Count, NBT);
}
}
else
{
// MC 1.13 after
byte WindowID = dataTypes.ReadNextByte(packetData);
short SlotID = dataTypes.ReadNextShort(packetData);
bool Present = dataTypes.ReadNextBool(packetData);
if (Present)
{
int ItemID = dataTypes.ReadNextVarInt(packetData);
byte Count = dataTypes.ReadNextByte(packetData);
Dictionary<string, object> NBT = dataTypes.ReadNextNbt(packetData);
handler.OnSetSlot(WindowID, SlotID, ItemID, Count, NBT);
}
else
{
handler.OnSlotClear(WindowID, SlotID);
}
}
byte windowID = dataTypes.ReadNextByte(packetData);
short slotID = dataTypes.ReadNextShort(packetData);
Item item = dataTypes.ReadNextItemSlot(packetData);
handler.OnSetSlot(windowID, slotID, item);
}
break;
case PacketIncomingType.ResourcePackSend:
@ -1285,7 +1219,7 @@ namespace MinecraftClient.Protocol.Handlers
/// <param name="EntityID"></param>
/// <param name="type"></param>
/// <returns></returns>
public bool SendInteractEntityPacket(int EntityID, int type)
public bool SendInteractEntity(int EntityID, int type)
{
try
{
@ -1300,7 +1234,7 @@ namespace MinecraftClient.Protocol.Handlers
catch (ObjectDisposedException) { return false; }
}
// TODO: Interact at block location (e.g. chest minecart)
public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z, int hand)
public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand)
{
try
{
@ -1318,12 +1252,12 @@ namespace MinecraftClient.Protocol.Handlers
catch (System.IO.IOException) { return false; }
catch (ObjectDisposedException) { return false; }
}
public bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z)
public bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z)
{
return false;
}
public bool SendUseItemPacket(int hand)
public bool SendUseItem(int hand)
{
try
{
@ -1370,5 +1304,45 @@ namespace MinecraftClient.Protocol.Handlers
catch (System.IO.IOException) { return false; }
catch (ObjectDisposedException) { return false; }
}
public bool SendClickWindow(int windowId, int slotId, Item item)
{
try
{
short actionNumber = (short)(window_actions[windowId] + 1);
window_actions[windowId] = actionNumber;
List<byte> packet = new List<byte>();
packet.Add((byte)windowId);
packet.AddRange(dataTypes.GetShort((short)slotId));
packet.Add(0); // Left mouse click
packet.AddRange(dataTypes.GetShort(actionNumber));
// Operation mode = 0 (default)
if (protocolversion >= MC19Version)
packet.AddRange(dataTypes.GetVarInt(0));
else packet.Add(0);
packet.AddRange(dataTypes.GetItemSlot(item));
SendPacket(PacketOutgoingType.ClickWindow, packet);
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
catch (ObjectDisposedException) { return false; }
}
public bool SendCloseWindow(int windowId)
{
try
{
SendPacket(PacketOutgoingType.CloseWindow, new[] { (byte)windowId });
return true;
}
catch (SocketException) { return false; }
catch (System.IO.IOException) { return false; }
catch (ObjectDisposedException) { return false; }
}
}
}

View file

@ -306,6 +306,8 @@ namespace MinecraftClient.Protocol.Handlers
case PacketOutgoingType.HeldItemChange: return 0x17;
case PacketOutgoingType.InteractEntity: return 0x02;
case PacketOutgoingType.TeleportConfirm: throw new InvalidOperationException("Teleport confirm is not supported in protocol " + protocol);
case PacketOutgoingType.ClickWindow: return 0x0E;
case PacketOutgoingType.CloseWindow: return 0x0D;
}
}
else if (protocol <= Protocol18Handler.MC1112Version) // MC 1.9, 1,10 and 1.11
@ -324,6 +326,8 @@ namespace MinecraftClient.Protocol.Handlers
case PacketOutgoingType.TeleportConfirm: return 0x00;
case PacketOutgoingType.HeldItemChange: return 0x17;
case PacketOutgoingType.InteractEntity: return 0x0A;
case PacketOutgoingType.ClickWindow: return 0x07;
case PacketOutgoingType.CloseWindow: return 0x08;
}
}
else if (protocol <= Protocol18Handler.MC112Version) // MC 1.12
@ -342,6 +346,8 @@ namespace MinecraftClient.Protocol.Handlers
case PacketOutgoingType.TeleportConfirm: return 0x00;
case PacketOutgoingType.HeldItemChange: return 0x1A;
case PacketOutgoingType.InteractEntity: return 0x0B;
case PacketOutgoingType.ClickWindow: return 0x07;
case PacketOutgoingType.CloseWindow: return 0x08;
}
}
else if (protocol <= Protocol18Handler.MC1122Version) // 1.12.2
@ -360,6 +366,8 @@ namespace MinecraftClient.Protocol.Handlers
case PacketOutgoingType.TeleportConfirm: return 0x00;
case PacketOutgoingType.HeldItemChange: return 0x1F;
case PacketOutgoingType.InteractEntity: return 0x0A;
case PacketOutgoingType.ClickWindow: return 0x07;
case PacketOutgoingType.CloseWindow: return 0x08;
}
}
else if (protocol < Protocol18Handler.MC114Version) // MC 1.13 to 1.13.2
@ -378,6 +386,8 @@ namespace MinecraftClient.Protocol.Handlers
case PacketOutgoingType.TeleportConfirm: return 0x00;
case PacketOutgoingType.HeldItemChange: return 0x21;
case PacketOutgoingType.InteractEntity: return 0x0D;
case PacketOutgoingType.ClickWindow: return 0x08;
case PacketOutgoingType.CloseWindow: return 0x09;
}
}
else // MC 1.14 to 1.15
@ -398,6 +408,8 @@ namespace MinecraftClient.Protocol.Handlers
case PacketOutgoingType.InteractEntity: return 0x0E;
case PacketOutgoingType.UseItem: return 0x2D;
case PacketOutgoingType.PlayerBlockPlacement: return 0x2C;
case PacketOutgoingType.ClickWindow: return 0x09;
case PacketOutgoingType.CloseWindow: return 0x0A;
}
}

View file

@ -4,6 +4,7 @@ using System.Linq;
using System.Text;
using MinecraftClient.Crypto;
using MinecraftClient.Mapping;
using MinecraftClient.Inventory;
namespace MinecraftClient.Protocol
{
@ -98,7 +99,7 @@ namespace MinecraftClient.Protocol
/// <param name="EntityID">Entity ID to interact with</param>
/// <param name="type">Type of interaction (0: interact, 1: attack, 2: interact at)</param>
/// <returns>True if packet was successfully sent</returns>
bool SendInteractEntityPacket(int EntityID, int type);
bool SendInteractEntity(int EntityID, int type);
/// <summary>
/// Send an entity interaction packet to the server.
@ -110,7 +111,7 @@ namespace MinecraftClient.Protocol
/// <param name="Z">Z coordinate for "interact at"</param>
/// <param name="hand">Player hand (0: main hand, 1: off hand)</param>
/// <returns>True if packet was successfully sent</returns>
bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z, int hand);
bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z, int hand);
/// <summary>
/// Send an entity interaction packet to the server.
@ -121,14 +122,29 @@ namespace MinecraftClient.Protocol
/// <param name="Y">Y coordinate for "interact at"</param>
/// <param name="Z">Z coordinate for "interact at"</param>
/// <returns>True if packet was successfully sent</returns>
bool SendInteractEntityPacket(int EntityID, int type, float X, float Y, float Z);
bool SendInteractEntity(int EntityID, int type, float X, float Y, float Z);
/// <summary>
/// Send a use item packet to the server
/// </summary>
/// <param name="hand">0: main hand, 1: off hand</param>
/// <returns>True if packet was successfully sent</returns>
bool SendUseItemPacket(int hand);
bool SendUseItem(int hand);
/// <summary>
/// Send a click window slot packet to the server
/// </summary>
/// <param name="windowId">Id of the window being clicked</param>
/// <param name="slotId">Id of the clicked slot</param>
/// <param name="item">Item in the clicked slot</param>
/// <returns>True if packet was successfully sent</returns>
bool SendClickWindow(int windowId, int slotId, Item item);
/// <summary>
/// Send a close window packet to the server
/// </summary>
/// <param name="windowId">Id of the window being closed</param>
bool SendCloseWindow(int windowId);
/// <summary>
/// Send player block placement packet to the server

View file

@ -53,12 +53,12 @@ namespace MinecraftClient.Protocol
/// <summary>
/// Called when an inventory is opened
/// </summary>
void OnInventoryOpen(Container inventory);
void OnInventoryOpen(int inventoryID, Container inventory);
/// <summary>
/// Called when an inventory is closed
/// </summary>
void OnInventoryClose(byte inventoryID);
void OnInventoryClose(int inventoryID);
/// <summary>
/// Called when the player respawns, which happens on login, respawn and world change.
@ -199,26 +199,17 @@ namespace MinecraftClient.Protocol
/// <summary>
/// Called when inventory items have been received
/// </summary>
/// <param name="type">Inventory type</param>
/// <param name="inventoryID">Inventory ID</param>
/// <param name="itemList">Item list</param>
void OnWindowItems(int type, Dictionary<int, MinecraftClient.Inventory.Item> itemList);
/// <summary>
/// Called when a single slot has been cleared inside an inventory
/// </summary>
/// <param name="WindowID">Inventory ID</param>
/// <param name="SlotID">Slot ID</param>
void OnSlotClear(byte WindowID, short SlotID);
void OnWindowItems(byte inventoryID, Dictionary<int, MinecraftClient.Inventory.Item> itemList);
/// <summary>
/// Called when a single slot has been updated inside an inventory
/// </summary>
/// <param name="WindowID">Inventory ID</param>
/// <param name="SlotID">Slot ID</param>
/// <param name="ItemID">Item ID</param>
/// <param name="Count">Item Count</param>
/// <param name="NBT">Item Metadata</param>
void OnSetSlot(byte WindowID, short SlotID, int ItemID, byte Count, Dictionary<string, object> NBT);
/// <param name="inventoryID">Window ID</param>
/// <param name="slotID">Slot ID</param>
/// <param name="item">Item (may be null for empty slot)</param>
void OnSetSlot(byte inventoryID, short slotID, Item item);
/// <summary>
/// Called when the Player entity ID has been received from the server