namespace Minecraft.Server.FourKit.Inventory; using System.Runtime.InteropServices; using System.Text; using Minecraft.Server.FourKit.Entity; using Minecraft.Server.FourKit.Inventory.Meta; /// /// Represents a player's inventory, including armor slots and the held item. /// public class PlayerInventory : Inventory { private const int INVENTORY_SIZE = 40; private const int ARMOR_START = 36; private const int QUICKBAR_SIZE = 9; private int _heldItemSlot; internal HumanEntity? _holder; internal PlayerInventory() : base("Player", InventoryType.PLAYER, INVENTORY_SIZE) { } protected internal override void EnsureSynced() { if (_holder == null || NativeBridge.GetPlayerInventory == null) return; int entityId = _holder.getEntityId(); int[] buf = new int[121]; var gh = GCHandle.Alloc(buf, GCHandleType.Pinned); try { NativeBridge.GetPlayerInventory(entityId, gh.AddrOfPinnedObject()); } finally { gh.Free(); } for (int i = 0; i < INVENTORY_SIZE; i++) { int id = buf[i * 3]; int count = buf[i * 3 + 1]; int aux = buf[i * 3 + 2]; if (id > 0 && count > 0) { var stack = new ItemStack(id, count, (short)aux); var meta = ReadMetaFromNative(entityId, i); if (meta != null) stack.setItemMetaInternal(meta); _items[i] = stack; } else { _items[i] = null; } } _heldItemSlot = buf[120]; } /// public override void setItem(int index, ItemStack? item) { base.setItem(index, item); _slotModifiedByPlugin = true; if (_holder != null && NativeBridge.SetPlayerInventorySlot != null && index >= 0 && index < INVENTORY_SIZE) { int id = item?.getTypeId() ?? 0; int count = item?.getAmount() ?? 0; int aux = item?.getDurability() ?? 0; NativeBridge.SetPlayerInventorySlot(_holder.getEntityId(), index, id, count, aux); WriteMetaToNative(_holder.getEntityId(), index, item?.getItemMetaInternal()); } } /// /// Returns all ItemStacks from the armor slots. /// /// An array of ItemStacks for the armor slots. public ItemStack?[] getArmorContents() { EnsureSynced(); var armor = new ItemStack?[4]; for (int i = 0; i < 4; i++) armor[i] = _items[ARMOR_START + i]; return armor; } /// /// Gets the ItemStack in the helmet slot. /// /// The helmet ItemStack. public ItemStack? getHelmet() => getItem(ARMOR_START + 3); /// /// Gets the ItemStack in the chestplate slot. /// /// The chestplate ItemStack. public ItemStack? getChestplate() => getItem(ARMOR_START + 2); /// /// Gets the ItemStack in the leggings slot. /// /// The leggings ItemStack. public ItemStack? getLeggings() => getItem(ARMOR_START + 1); /// /// Gets the ItemStack in the boots slot. /// /// The boots ItemStack. public ItemStack? getBoots() => getItem(ARMOR_START); /// /// Sets all four armor slots at once. /// /// An array of ItemStacks for the armor slots. public void setArmorContents(ItemStack?[] items) { int len = Math.Min(items.Length, 4); for (int i = 0; i < len; i++) setItem(ARMOR_START + i, items[i]); } /// /// Sets the helmet slot. /// /// The ItemStack to set. public void setHelmet(ItemStack? helmet) => setItem(ARMOR_START + 3, helmet); /// /// Sets the chestplate slot. /// /// The ItemStack to set. public void setChestplate(ItemStack? chestplate) => setItem(ARMOR_START + 2, chestplate); /// /// Sets the leggings slot. /// /// The ItemStack to set. public void setLeggings(ItemStack? leggings) => setItem(ARMOR_START + 1, leggings); /// /// Sets the boots slot. /// /// The ItemStack to set. public void setBoots(ItemStack? boots) => setItem(ARMOR_START, boots); /// /// Gets the item the player is currently holding. /// /// The held ItemStack. public ItemStack? getItemInHand() => getItem(_heldItemSlot); /// /// Sets the item in the player's hand. /// /// The ItemStack to set. public void setItemInHand(ItemStack? stack) { EnsureSynced(); setItem(_heldItemSlot, stack); } /// /// Gets the slot number of the currently held item. /// /// The held item slot index (0-8). public int getHeldItemSlot() { EnsureSynced(); return _heldItemSlot; } /// /// Sets the slot number of the currently held item. /// /// The slot index (0-8). public void setHeldItemSlot(int slot) { if (slot < 0 || slot >= QUICKBAR_SIZE) throw new ArgumentException($"Slot must be between 0 and {QUICKBAR_SIZE - 1} inclusive."); _heldItemSlot = slot; if (_holder != null) NativeBridge.SetHeldItemSlot?.Invoke(_holder.getEntityId(), slot); } /// /// Clears all matching items from the inventory. Setting either value /// to -1 will skip its check, while setting both to -1 will clear all /// items in your inventory unconditionally. /// /// The material id to match, or -1 for any. /// The data value to match, or -1 for any. /// The number of stacks cleared. public int clear(int id, int data) { EnsureSynced(); int count = 0; for (int i = 0; i < getSize(); i++) { var item = _items[i]; if (item == null) continue; if (id != -1 && item.getTypeId() != id) continue; if (data != -1 && item.getDurability() != data) continue; setItem(i, null); count++; } return count; } /// /// Gets the holder of this inventory. /// /// The HumanEntity that owns this inventory. public HumanEntity? getHolder() => _holder; private static ItemMeta? ReadMetaFromNative(int entityId, int slot) { if (NativeBridge.GetItemMeta == null) return null; byte[] buf = new byte[4096]; int bytesWritten; var gh = GCHandle.Alloc(buf, GCHandleType.Pinned); try { bytesWritten = NativeBridge.GetItemMeta(entityId, slot, gh.AddrOfPinnedObject(), buf.Length); } finally { gh.Free(); } if (bytesWritten <= 0) return null; int offset = 0; int nameLen = BitConverter.ToInt32(buf, offset); offset += 4; string? displayName = null; if (nameLen > 0) { displayName = Encoding.UTF8.GetString(buf, offset, nameLen); offset += nameLen; } int loreCount = 0; if (offset + 4 <= bytesWritten) { loreCount = BitConverter.ToInt32(buf, offset); offset += 4; } List? lore = null; if (loreCount > 0) { lore = new List(loreCount); for (int i = 0; i < loreCount; i++) { if (offset + 4 > bytesWritten) break; int lineLen = BitConverter.ToInt32(buf, offset); offset += 4; if (lineLen > 0 && offset + lineLen <= bytesWritten) { lore.Add(Encoding.UTF8.GetString(buf, offset, lineLen)); offset += lineLen; } else { lore.Add(string.Empty); } } } if (displayName == null && (lore == null || lore.Count == 0)) return null; var meta = new ItemMeta(); if (displayName != null) meta.setDisplayName(displayName); if (lore != null && lore.Count > 0) meta.setLore(lore); return meta; } private static void WriteMetaToNative(int entityId, int slot, ItemMeta? meta) { if (NativeBridge.SetItemMeta == null) return; if (meta == null || meta.isEmpty()) { NativeBridge.SetItemMeta(entityId, slot, IntPtr.Zero, 0); return; } using var ms = new System.IO.MemoryStream(512); using var bw = new System.IO.BinaryWriter(ms); if (meta.hasDisplayName()) { byte[] nameBytes = Encoding.UTF8.GetBytes(meta.getDisplayName()); bw.Write(nameBytes.Length); bw.Write(nameBytes); } else { bw.Write(0); } if (meta.hasLore()) { var lore = meta.getLore(); bw.Write(lore.Count); foreach (var line in lore) { byte[] lineBytes = Encoding.UTF8.GetBytes(line ?? string.Empty); bw.Write(lineBytes.Length); bw.Write(lineBytes); } } else { bw.Write(0); } bw.Flush(); byte[] data = ms.ToArray(); var gh = GCHandle.Alloc(data, GCHandleType.Pinned); try { NativeBridge.SetItemMeta(entityId, slot, gh.AddrOfPinnedObject(), data.Length); } finally { gh.Free(); } } }