namespace Minecraft.Server.FourKit.Inventory; using System.Collections; using System.Collections.Generic; using System.Runtime.InteropServices; using Minecraft.Server.FourKit.Entity; /// /// Represents an inventory containing items. Behavior relating to /// is unspecified. /// public class Inventory : IEnumerable { internal static bool _slotModifiedByPlugin; private readonly string _name; private readonly InventoryType _type; internal readonly ItemStack?[] _items; private readonly int _nativeEntityId = -1; internal Inventory(string name, InventoryType type, int size) { _name = name; _type = type; _items = new ItemStack?[size]; } internal Inventory(string name, InventoryType type, int size, int entityId) : this(name, type, size) { _nativeEntityId = entityId; } protected internal virtual void EnsureSynced() { if (_nativeEntityId < 0 || NativeBridge.GetContainerContents == null) return; int[] buf = new int[_items.Length * 3]; var gh = GCHandle.Alloc(buf, GCHandleType.Pinned); try { NativeBridge.GetContainerContents(_nativeEntityId, gh.AddrOfPinnedObject(), _items.Length); } finally { gh.Free(); } for (int i = 0; i < _items.Length; i++) { int id = buf[i * 3 + 0]; int aux = buf[i * 3 + 1]; int packed = buf[i * 3 + 2]; ushort count = (ushort)((packed >> 8) & 0xFFFF); //byte flags = (byte)((packed >> 24) & 0xFF); //bool hasMetadata = (flags & 0x1) != 0; //unused here _items[i]?.UnbindFromInventory(); if (id > 0 && count > 0) { if (_items[i] == null) { _items[i] = new ItemStack(id, count, (short)aux); } else { _items[i]!.setTypeId(id); _items[i]!.setAmount(count); _items[i]!.setDurability((short)aux); } _items[i]!.BindToInventory(this, i); //should we unbind and rebind or just keep the bind? } else { _items[i] = null; } } } /// /// Returns the size of the inventory. /// /// The size of the inventory. public int getSize() => _items.Length; /// /// Returns the name of the inventory. /// /// The String with the name of the inventory. public string getName() => _name; /// /// Returns the ItemStack found in the slot at the given index. /// /// The index of the Slot's ItemStack to return. /// The ItemStack in the slot. public virtual ItemStack? getItem(int index) { EnsureSynced(); if (index < 0 || index >= _items.Length) return null; var item = _items[index]; item?.BindToInventory(this, index); return item; } /// /// Stores the ItemStack at the given index of the inventory. /// /// The index where to put the ItemStack. /// The ItemStack to set. public virtual void setItem(int index, ItemStack? item) { if (index >= 0 && index < _items.Length) { var old = _items[index]; if (old != item) { old?.UnbindFromInventory(); item?.BindToInventory(this, index); } _items[index] = item; _slotModifiedByPlugin = true; } if (_nativeEntityId >= 0 && NativeBridge.SetContainerSlot != null && index >= 0 && index < _items.Length) { int id = item?.getTypeId() ?? 0; int count = item?.getAmount() ?? 0; int aux = item?.getDurability() ?? 0; NativeBridge.SetContainerSlot(_nativeEntityId, index, id, count, aux); } } /// /// Stores the given ItemStacks in the inventory. This will try to fill /// existing stacks and empty slots as well as it can. /// The returned Dictionary contains what it couldn't store, where the key /// is the index of the parameter, and the value is the ItemStack at that /// index of the params parameter. /// /// The ItemStacks to add. /// A Dictionary containing items that didn't fit. public Dictionary addItem(params ItemStack[] items) { EnsureSynced(); var leftover = new Dictionary(); for (int i = 0; i < items.Length; i++) { var toAdd = items[i]; if (toAdd == null) continue; int remaining = toAdd.getAmount(); for (int slot = 0; slot < _items.Length && remaining > 0; slot++) { var existing = _items[slot]; if (existing != null && existing.getType() == toAdd.getType() && existing.getDurability() == toAdd.getDurability()) { int canFit = 64 - existing.getAmount(); if (canFit > 0) { int added = Math.Min(canFit, remaining); existing.setAmount(existing.getAmount() + added); setItem(slot, existing); remaining -= added; } } } for (int slot = 0; slot < _items.Length && remaining > 0; slot++) { if (_items[slot] == null) { int added = Math.Min(64, remaining); setItem(slot, new ItemStack(toAdd.getType(), added, toAdd.getDurability())); remaining -= added; } } if (remaining > 0) leftover[i] = new ItemStack(toAdd.getType(), remaining, toAdd.getDurability()); } return leftover; } /// /// Removes the given ItemStacks from the inventory. /// It will try to remove 'as much as possible' from the types and amounts /// you give as arguments. /// The returned Dictionary contains what it couldn't remove. /// /// The ItemStacks to remove. /// A Dictionary containing items that couldn't be removed. public Dictionary removeItem(params ItemStack[] items) { EnsureSynced(); var leftover = new Dictionary(); for (int i = 0; i < items.Length; i++) { var toRemove = items[i]; if (toRemove == null) continue; int remaining = toRemove.getAmount(); for (int slot = 0; slot < _items.Length && remaining > 0; slot++) { var existing = _items[slot]; if (existing != null && existing.getType() == toRemove.getType() && existing.getDurability() == toRemove.getDurability()) { int removed = Math.Min(existing.getAmount(), remaining); existing.setAmount(existing.getAmount() - removed); remaining -= removed; if (existing.getAmount() <= 0) setItem(slot, null); else setItem(slot, existing); } } if (remaining > 0) leftover[i] = new ItemStack(toRemove.getType(), remaining, toRemove.getDurability()); } return leftover; } /// /// Returns all ItemStacks from the inventory. /// /// An array of ItemStacks from the inventory. public ItemStack?[] getContents() { EnsureSynced(); return (ItemStack?[])_items.Clone(); } /// /// Completely replaces the inventory's contents. Removes all existing /// contents and replaces it with the ItemStacks given in the array. /// /// A complete replacement for the contents; the length must /// be less than or equal to . public void setContents(ItemStack?[] items) { int len = Math.Min(items.Length, _items.Length); for (int i = 0; i < _items.Length; i++) setItem(i, i < len ? items[i] : null); } /// /// Checks if the inventory contains any ItemStacks with the given material id. /// /// The material id to check for. /// true if an ItemStack in this inventory contains the material id. public bool contains(int materialId) { EnsureSynced(); foreach (var item in _items) if (item != null && item.getTypeId() == materialId) return true; return false; } /// /// Checks if the inventory contains any ItemStacks with the given material. /// /// The material to check for. /// true if an ItemStack is found with the given Material. public bool contains(Material material) { EnsureSynced(); foreach (var item in _items) if (item != null && item.getType() == material) return true; return false; } /// /// Checks if the inventory contains any ItemStacks matching the given ItemStack. /// This will only return true if both the type and the amount of the stack match. /// /// The ItemStack to match against. /// false if item is null, true if any exactly matching ItemStacks were found. public bool contains(ItemStack? item) { if (item == null) return false; EnsureSynced(); foreach (var slot in _items) if (slot != null && slot.getType() == item.getType() && slot.getAmount() == item.getAmount() && slot.getDurability() == item.getDurability()) return true; return false; } /// /// Checks if the inventory contains any ItemStacks with the given material id, /// adding to at least the minimum amount specified. /// /// The material id to check for. /// The minimum amount to look for. /// true if this contains any matching ItemStack with the given material id and amount. public bool contains(int materialId, int amount) { EnsureSynced(); int total = 0; foreach (var item in _items) if (item != null && item.getTypeId() == materialId) total += item.getAmount(); return total >= amount; } /// /// Checks if the inventory contains any ItemStacks with the given material, /// adding to at least the minimum amount specified. /// /// The material to check for. /// The minimum amount. /// true if enough ItemStacks were found to add to the given amount. public bool contains(Material material, int amount) { if (amount <= 0) return true; EnsureSynced(); int total = 0; foreach (var item in _items) if (item != null && item.getType() == material) total += item.getAmount(); return total >= amount; } /// /// Checks if the inventory contains at least the minimum amount specified /// of exactly matching ItemStacks. An ItemStack only counts if both the type /// and the amount of the stack match. /// /// The ItemStack to match against. /// How many identical stacks to check for. /// false if item is null, true if amount of exactly matching ItemStacks were found. public bool contains(ItemStack? item, int amount) { if (item == null) return false; if (amount <= 0) return true; EnsureSynced(); int count = 0; foreach (var slot in _items) if (slot != null && slot.getType() == item.getType() && slot.getAmount() == item.getAmount() && slot.getDurability() == item.getDurability()) count++; return count >= amount; } /// /// Checks if the inventory contains ItemStacks matching the given ItemStack /// whose amounts sum to at least the minimum amount specified. /// /// The ItemStack to match against. /// The minimum amount. /// false if item is null, true if enough ItemStacks were found to add to the given amount. public bool containsAtLeast(ItemStack? item, int amount) { if (item == null) return false; if (amount <= 0) return true; EnsureSynced(); int total = 0; foreach (var slot in _items) if (slot != null && slot.getType() == item.getType() && slot.getDurability() == item.getDurability()) total += slot.getAmount(); return total >= amount; } /// /// Returns a Dictionary with all slots and ItemStacks in the inventory with given material id. /// /// The material id to look for. /// A Dictionary containing the slot index, ItemStack pairs. public Dictionary all(int materialId) { EnsureSynced(); var result = new Dictionary(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getTypeId() == materialId) result[i] = _items[i]!; return result; } /// /// Returns a Dictionary with all slots and ItemStacks in the inventory with the given Material. /// /// The material to look for. /// A Dictionary containing the slot index, ItemStack pairs. public Dictionary all(Material material) { EnsureSynced(); var result = new Dictionary(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getType() == material) result[i] = _items[i]!; return result; } /// /// Finds all slots in the inventory containing any ItemStacks with the given ItemStack. /// This will only match slots if both the type and the amount of the stack match. /// /// The ItemStack to match against. /// A dictionary from slot indexes to item at index. public Dictionary all(ItemStack? item) { var result = new Dictionary(); if (item == null) return result; EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getType() == item.getType() && _items[i]!.getAmount() == item.getAmount() && _items[i]!.getDurability() == item.getDurability()) result[i] = _items[i]!; return result; } /// /// Finds the first slot in the inventory containing an ItemStack with the given material id. /// /// The material id to look for. /// The slot index of the given material id or -1 if not found. public int first(int materialId) { EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getTypeId() == materialId) return i; return -1; } /// /// Finds the first slot in the inventory containing an ItemStack with the given material. /// /// The material to look for. /// The slot index of the given Material or -1 if not found. public int first(Material material) { EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getType() == material) return i; return -1; } /// /// Returns the first slot in the inventory containing an ItemStack with the given stack. /// This will only match a slot if both the type and the amount of the stack match. /// /// The ItemStack to match against. /// The slot index of the given ItemStack or -1 if not found. public int first(ItemStack? item) { if (item == null) return -1; EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getType() == item.getType() && _items[i]!.getAmount() == item.getAmount() && _items[i]!.getDurability() == item.getDurability()) return i; return -1; } /// /// Returns the first empty Slot. /// /// The first empty Slot found, or -1 if no empty slots. public int firstEmpty() { EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] == null) return i; return -1; } /// /// Removes all stacks in the inventory matching the given material id. /// /// The material to remove. public void remove(int materialId) { EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getTypeId() == materialId) setItem(i, null); } /// /// Removes all stacks in the inventory matching the given material. /// /// The material to remove. public void remove(Material material) { EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getType() == material) setItem(i, null); } /// /// Removes all stacks in the inventory matching the given stack. /// This will only match a slot if both the type and the amount of the stack match. /// /// The ItemStack to match against. public void remove(ItemStack? item) { if (item == null) return; EnsureSynced(); for (int i = 0; i < _items.Length; i++) if (_items[i] != null && _items[i]!.getType() == item.getType() && _items[i]!.getAmount() == item.getAmount() && _items[i]!.getDurability() == item.getDurability()) setItem(i, null); } /// /// Clears out a particular slot in the index. /// /// The index to empty. public void clear(int index) { setItem(index, null); } /// /// Clears out the whole Inventory. /// public void clear() { for (int i = 0; i < _items.Length; i++) setItem(i, null); } /// /// Returns the title of this inventory. /// /// A String with the title. public string getTitle() => _name; /// /// Returns what type of inventory this is. /// /// The InventoryType representing the type of inventory. public InventoryType getType() => _type; /// /// Gets a list of players viewing the inventory. /// /// A list of HumanEntities who are viewing this Inventory. public virtual List getViewers() { if (_nativeEntityId < 0 || NativeBridge.GetContainerViewerEntityIds == null) return new List(); int[] ids = new int[64]; var ghIds = GCHandle.Alloc(ids, GCHandleType.Pinned); int[] countBuf = new int[1]; var ghCount = GCHandle.Alloc(countBuf, GCHandleType.Pinned); try { NativeBridge.GetContainerViewerEntityIds(_nativeEntityId, ghIds.AddrOfPinnedObject(), 64, ghCount.AddrOfPinnedObject()); } finally { ghIds.Free(); ghCount.Free(); } int count = countBuf[0]; var viewers = new List(); for (int i = 0; i < count; i++) { var player = FourKit.GetPlayerByEntityId(ids[i]); if (player != null) viewers.Add(player); } return viewers; } /// /// Returns an iterator starting at the given index. /// /// The index. /// An enumerator. public IEnumerator iterator(int index) { EnsureSynced(); int start = index >= 0 ? index : _items.Length + index; return GetEnumeratorFrom(start); } /// public IEnumerator GetEnumerator() { EnsureSynced(); return GetEnumeratorFrom(0); } IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); private IEnumerator GetEnumeratorFrom(int start) { for (int i = start; i < _items.Length; i++) if (_items[i] != null) { _items[i]!.BindToInventory(this, i); yield return _items[i]!; } } }