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]!;
}
}
}