package com.bergerkiller.bukkit.common.utils; import org.bukkit.Material; import org.bukkit.craftbukkit.inventory.CraftItemStack; import org.bukkit.inventory.Inventory; import com.bergerkiller.bukkit.common.conversion.Conversion; import com.bergerkiller.bukkit.common.internal.CommonNMS; import com.bergerkiller.bukkit.common.inventory.InventoryBaseImpl; import com.bergerkiller.bukkit.common.inventory.ItemParser; import com.bergerkiller.bukkit.common.nbt.CommonTagCompound; import com.bergerkiller.bukkit.common.nbt.CommonTagList; import com.bergerkiller.bukkit.common.reflection.classes.ItemStackRef; import net.minecraft.server.EntityItem; import net.minecraft.server.Item; import net.minecraft.server.ItemStack; /** * Contains item stack, item and inventory utilities */ public class ItemUtil { /** * Tests if the given ItemStacks can be fully transferred to another array of ItemStacks * * @param from ItemStack source array * @param to destination Inventory * @return True if full transfer was possible, False if not */ public static boolean canTransferAll(org.bukkit.inventory.ItemStack[] from, Inventory to) { return canTransferAll(from, to.getContents()); } /** * Tests if the given ItemStacks can be fully transferred to another array of ItemStacks * * @param from ItemStack source array * @param to ItemStack destination array * @return True if full transfer was possible, False if not */ public static boolean canTransferAll(org.bukkit.inventory.ItemStack[] from, org.bukkit.inventory.ItemStack[] to) { Inventory invto = new InventoryBaseImpl(to, true); for (org.bukkit.inventory.ItemStack item : cloneItems(from)) { transfer(item, invto, Integer.MAX_VALUE); if (!LogicUtil.nullOrEmpty(item)) { return false; } } return true; } /** * Tests if the given ItemStack can be transferred to the Inventory * * @return The amount that could be transferred */ public static int testTransfer(org.bukkit.inventory.ItemStack from, Inventory to) { if (LogicUtil.nullOrEmpty(from)) { return 0; } int startAmount = from.getAmount(); int fromAmount = startAmount; for (org.bukkit.inventory.ItemStack item : to.getContents()) { if (LogicUtil.nullOrEmpty(item)) { // Full transfer is possible (empty slot) fromAmount -= getMaxSize(from); } else if (equalsIgnoreAmount(from, item)) { // Stack transfer is possible (same item slot) fromAmount -= getMaxSize(item) - item.getAmount(); } if (fromAmount <= 0) { // All items could be transferred! return startAmount; } } return startAmount - fromAmount; } /** * Tests if the two items can be merged * * @return The amount that could be transferred */ public static int testTransfer(org.bukkit.inventory.ItemStack from, org.bukkit.inventory.ItemStack to) { if (LogicUtil.nullOrEmpty(from)) { return 0; } if (LogicUtil.nullOrEmpty(to)) { return Math.min(from.getAmount(), getMaxSize(from)); } if (equalsIgnoreAmount(from, to)) { return Math.min(from.getAmount(), getMaxSize(to) - to.getAmount()); } return 0; } /** * Transfers all ItemStacks from one Inventory to another * * @param from The Inventory to take ItemStacks from * @param to The Inventory to transfer to * @param maxAmount The maximum amount of items to transfer, -1 for infinite * @param parser The item parser used to set what items to transfer. Can be null. * @return The amount of items that got transferred */ public static int transfer(Inventory from, Inventory to, ItemParser parser, int maxAmount) { int startAmount = maxAmount < 0 ? Integer.MAX_VALUE : maxAmount; int amountToTransfer = startAmount; int tmptrans; for (int i = 0; i < from.getSize() && amountToTransfer > 0; i++) { org.bukkit.inventory.ItemStack item = from.getItem(i); if (LogicUtil.nullOrEmpty(item) || (parser != null && !parser.match(item))) { continue; } tmptrans = transfer(item, to, amountToTransfer); if (tmptrans > 0) { amountToTransfer -= tmptrans; from.setItem(i, item); } } return startAmount - amountToTransfer; } /** * Transfers the given ItemStack to multiple slots in the Inventory * * @param from The ItemStack to transfer * @param to The Inventory to transfer to * @param maxAmount The maximum amount of the item to transfer, -1 for infinite * @return The amount of the item that got transferred */ public static int transfer(org.bukkit.inventory.ItemStack from, Inventory to, int maxAmount) { int startAmount = maxAmount < 0 ? Integer.MAX_VALUE : maxAmount; if (startAmount == 0 || LogicUtil.nullOrEmpty(from)) { return 0; } int tmptrans; int amountToTransfer = startAmount; // try to stack to already existing items org.bukkit.inventory.ItemStack toitem; for (int i = 0; i < to.getSize(); i++) { toitem = to.getItem(i); if (!LogicUtil.nullOrEmpty(toitem)) { tmptrans = transfer(from, toitem, amountToTransfer); if (tmptrans > 0) { amountToTransfer -= tmptrans; to.setItem(i, toitem); // everything done? if (amountToTransfer <= 0 || LogicUtil.nullOrEmpty(from)){ break; } } } } // try to add it to empty slots if (amountToTransfer > 0 && from.getAmount() > 0) { for (int i = 0; i < to.getSize(); i++) { toitem = to.getItem(i); if (LogicUtil.nullOrEmpty(toitem)) { toitem = emptyItem(); // Transfer tmptrans = transfer(from, toitem, amountToTransfer); if (tmptrans > 0) { amountToTransfer -= tmptrans; to.setItem(i, toitem); // everything done? if (amountToTransfer <= 0 || LogicUtil.nullOrEmpty(from)) { break; } } } } } return startAmount - amountToTransfer; } /** * Tries to transfer items from the Inventory to the ItemStack * * @param from The Inventory to take an ItemStack from * @param to The ItemStack to merge the item taken * @param parser The item parser used to set what item to transfer if the receiving item is empty. Can be null. * @param maxAmount The maximum amount of the item to transfer, -1 for infinite * @return The amount of the item that got transferred */ public static int transfer(Inventory from, org.bukkit.inventory.ItemStack to, ItemParser parser, int maxAmount) { int startAmount = maxAmount < 0 ? Integer.MAX_VALUE : maxAmount; int amountToTransfer = startAmount; for (int i = 0; i < from.getSize() && amountToTransfer > 0; i++) { org.bukkit.inventory.ItemStack item = from.getItem(i); if (LogicUtil.nullOrEmpty(item)) { continue; } if (LogicUtil.nullOrEmpty(to)) { // Parser matching if (parser != null && !parser.match(item)) { continue; } // Set item info to this item transferInfo(item, to); } amountToTransfer -= transfer(item, to, amountToTransfer); from.setItem(i, item); } return startAmount - amountToTransfer; } /** * Merges two ItemStacks together<br> * - If from is empty or null, no transfer happens<br> * - If to is null, no transfer happens<br> * - If to is empty, full transfer occurs * * @param from The ItemStack to merge * @param to The receiving ItemStack * @param maxAmount The maximum amount of the item to transfer, -1 for infinite * @return The amount of the item that got transferred */ public static int transfer(org.bukkit.inventory.ItemStack from, org.bukkit.inventory.ItemStack to, int maxAmount) { if (LogicUtil.nullOrEmpty(from) || to == null) { return 0; } int amountToTransfer = Math.min(maxAmount < 0 ? Integer.MAX_VALUE : maxAmount, from.getAmount()); // Transfering to an empty item, don't bother doing any stacking logic if (LogicUtil.nullOrEmpty(to)) { // Limit amount by maximum of the from item amountToTransfer = Math.min(amountToTransfer, getMaxSize(from)); if (amountToTransfer <= 0) { return 0; } // Transfer item information transferInfo(from, to); // Transfer the amount to.setAmount(amountToTransfer); subtractAmount(from, amountToTransfer); return amountToTransfer; } // Can we stack? amountToTransfer = Math.min(amountToTransfer, getMaxSize(to) - to.getAmount()); if (amountToTransfer <= 0 || !equalsIgnoreAmount(from, to)) { return 0; } // From and to are equal, we can now go ahead and stack them addAmount(to, amountToTransfer); subtractAmount(from, amountToTransfer); return amountToTransfer; } /** * Transfers the item type, data and enchantments from one item stack to the other * * @param from which Item Stack to read the info * @param to which Item Stack to transfer the info to */ @SuppressWarnings("deprecation") public static void transferInfo(org.bukkit.inventory.ItemStack from, org.bukkit.inventory.ItemStack to) { // Transfer type, durability and any other remaining metadata information to.setTypeId(from.getTypeId()); to.setDurability(from.getDurability()); setMetaTag(to, LogicUtil.clone(getMetaTag(from))); } /** * Checks whether two item stacks equal, while ignoring the item amounts * * @param item1 to check * @param item2 to check * @return True if the items have the same type, data and enchantments, False if not */ public static boolean equalsIgnoreAmount(org.bukkit.inventory.ItemStack item1, org.bukkit.inventory.ItemStack item2) { if (MaterialUtil.getTypeId(item1) != MaterialUtil.getTypeId(item2) || MaterialUtil.getRawData(item1) != MaterialUtil.getRawData(item2)) { return false; } // Metadata checks boolean hasMeta = hasMetaTag(item1); if (hasMeta != hasMetaTag(item2)) { return false; } else if (!hasMeta) { // No further data to test return true; } if (!item1.getItemMeta().equals(item2.getItemMeta())) { return false; } // Not included in metadata checks: Item attributes (Bukkit needs to update) CommonTagList item1Attr = getMetaTag(item1).get("AttributeModifiers", CommonTagList.class); CommonTagList item2Attr = getMetaTag(item2).get("AttributeModifiers", CommonTagList.class); return LogicUtil.bothNullOrEqual(item1Attr, item2Attr); } /** * Removes certain kinds of items from an inventory * * @param inventory to remove items from * @param item signature of the items to remove */ public static void removeItems(Inventory inventory, org.bukkit.inventory.ItemStack item) { removeItems(inventory, MaterialUtil.getTypeId(item), MaterialUtil.getRawData(item), item.getAmount()); } /** * Removes certain kinds of items from an inventory * * @param inventory to remove items from * @param itemid of the items to remove * @param data of the items to remove, -1 for any data * @param amount of items to remove, -1 for infinite amount */ public static void removeItems(Inventory inventory, int itemid, int data, int amount) { int countToRemove = amount < 0 ? Integer.MAX_VALUE : amount; for (int i = 0; i < inventory.getSize(); i++) { org.bukkit.inventory.ItemStack item = inventory.getItem(i); if (LogicUtil.nullOrEmpty(item) || MaterialUtil.getTypeId(item) != itemid || (data != -1 && MaterialUtil.getRawData(item) != data)) { continue; } if (item.getAmount() <= countToRemove) { countToRemove -= item.getAmount(); inventory.setItem(i, null); } else { subtractAmount(item, countToRemove); countToRemove = 0; inventory.setItem(i, item); break; } } } /** * Obtains an empty item stack that allows mutual changes<br> * This is a CraftItemStack with a NMS ItemStack as buffer * * @return Empty item stack */ public static org.bukkit.inventory.ItemStack emptyItem() { return CraftItemStack.asCraftMirror((ItemStack) ItemStackRef.newInstance(Material.AIR, 0, 0)); } /** * Kills the old item and spawns a new item in it's place * * @param item to respawn * @return Respawned item */ public static org.bukkit.entity.Item respawnItem(org.bukkit.entity.Item item) { item.remove(); EntityItem oldItemHandle = CommonNMS.getNative(item); EntityItem newItemHandle = new EntityItem(oldItemHandle.world, oldItemHandle.locX, oldItemHandle.locY, oldItemHandle.locZ, oldItemHandle.getItemStack()); newItemHandle.fallDistance = oldItemHandle.fallDistance; newItemHandle.fireTicks = oldItemHandle.fireTicks; newItemHandle.pickupDelay = oldItemHandle.pickupDelay; newItemHandle.motX = oldItemHandle.motX; newItemHandle.motY = oldItemHandle.motY; newItemHandle.motZ = oldItemHandle.motZ; newItemHandle.age = oldItemHandle.age; newItemHandle.world.addEntity(newItemHandle); return CommonNMS.getItem(newItemHandle); } /** * Gets the contents of an inventory, cloning all the items * * @param inventory to get the cloned contents of * @return Cloned inventory contents array */ public static org.bukkit.inventory.ItemStack[] getClonedContents(Inventory inventory) { org.bukkit.inventory.ItemStack[] rval = new org.bukkit.inventory.ItemStack[inventory.getSize()]; for (int i = 0; i < rval.length; i++) { rval[i] = cloneItem(inventory.getItem(i)); } return rval; } /** * Clones a single item * * @param stack to be cloned, can be null * @return Cloned item stack */ public static org.bukkit.inventory.ItemStack cloneItem(org.bukkit.inventory.ItemStack stack) { return LogicUtil.clone(stack); } /** * Creates a new itemstack array containing items not referencing the input item stacks * * @param input array to process * @return Cloned item stack array */ public static org.bukkit.inventory.ItemStack[] cloneItems(org.bukkit.inventory.ItemStack[] input) { return LogicUtil.cloneAll(input); } /** * Subtracts a certain amount from an item, without limiting to the max stack size * * @param item * @param amount to subtract */ public static void subtractAmount(org.bukkit.inventory.ItemStack item, int amount) { addAmount(item, -amount); } /** * Adds a certain amount to an item, without limiting to the max stack size * * @param item * @param amount to add */ public static void addAmount(org.bukkit.inventory.ItemStack item, int amount) { item.setAmount(Math.max(item.getAmount() + amount, 0)); } /** * Obtains an item of the given type and data in the inventory specified<br> * If multiple items with the same type and data exist, their amounts are added together * * @param inventory to look in * @param typeId of the items to look for, -1 for any item * @param data of the items to look for, -1 for any data * @return Amount of items in the inventory */ public static org.bukkit.inventory.ItemStack findItem(Inventory inventory, int typeId, int data) { org.bukkit.inventory.ItemStack rval = null; int itemData = data; int itemTypeId = typeId; for (org.bukkit.inventory.ItemStack item : inventory.getContents()) { if (LogicUtil.nullOrEmpty(item)) { continue; } // Compare type Id if (itemTypeId == -1) { itemTypeId = MaterialUtil.getTypeId(item); } else if (itemTypeId != MaterialUtil.getTypeId(item)) { continue; } // Compare data if (itemData == -1) { itemData = MaterialUtil.getRawData(item); } else if (MaterialUtil.getRawData(item) != itemData) { continue; } // addition if (rval == null) { rval = item.clone(); } else { addAmount(rval, item.getAmount()); } } return rval; } /** * Gets the total item count of a given type and data * * @param inventory to look in * @param typeid of the items to look for, -1 for any item * @param data of the items to look for, -1 for any data * @return Amount of items in the inventory */ public static int getItemCount(Inventory inventory, int typeid, int data) { if (typeid < 0) { int count = 0; for (org.bukkit.inventory.ItemStack item : inventory.getContents()) { if (!LogicUtil.nullOrEmpty(item)) { count += item.getAmount(); } } return count; } else { org.bukkit.inventory.ItemStack rval = findItem(inventory, typeid, data); return rval == null ? 0 : rval.getAmount(); } } /** * Gets the max stacking size for a given item * * @param itemId of the item * @param def to return for invalid items * @return max stacking size */ public static int getMaxSize(int itemId, int def) { return getMaxSize(MaterialUtil.getType(itemId), def); } /** * Gets the max stacking size for a given item * * @param itemType of the item * @param def to return for invalid items * @return max stacking size */ public static int getMaxSize(Material itemType, int def) { Item item = CommonNMS.getItem(itemType); return item == null ? def : item.getMaxStackSize(); } /** * Gets the max stacking size for a given item * * @param stack to get the max stacked size * @return max stacking size */ public static int getMaxSize(org.bukkit.inventory.ItemStack stack) { if (LogicUtil.nullOrEmpty(stack)) { return 0; } else { return getMaxSize(MaterialUtil.getTypeId(stack), 0); } } /** * Checks whether an Item stores a metadata tag * * @param stack to check * @return True if a metadata tag is stored, False if not */ public static boolean hasMetaTag(org.bukkit.inventory.ItemStack stack) { return CommonNMS.getNative(stack).hasTag(); } /** * Obtains the Metadata tag stored in an item. * If no metadata is stored, null is returned instead. * * @param stack to get the metadata tag of * @return metadata tag */ public static CommonTagCompound getMetaTag(org.bukkit.inventory.ItemStack stack) { return ItemStackRef.tag.get(Conversion.toItemStackHandle.convert(stack)); } /** * Sets the Metadata tag stored in an item. * If tag is null, all metadata is cleared. * * @param stack to set the metadata tag of * @param tag to set to */ public static void setMetaTag(org.bukkit.inventory.ItemStack stack, CommonTagCompound tag) { ItemStackRef.tag.set(Conversion.toItemStackHandle.convert(stack), tag); } /** * Sets the cost of repairing this item * * @param stack to set the repair cost of * @param repairCost to set to */ public static void setRepairCost(org.bukkit.inventory.ItemStack stack, int repairCost) { CommonNMS.getNative(stack).setRepairCost(repairCost); } /** * Gets the cost of repairing this item * * @param stack to get the repair cost of * @return repair cost */ public static int getRepairCost(org.bukkit.inventory.ItemStack stack) { return CommonNMS.getNative(stack).getRepairCost(); } /** * Checks whether an item has a custom display name set * * @param stack to check * @return True if a custom name is set, False if not */ public static boolean hasDisplayName(org.bukkit.inventory.ItemStack stack) { return CommonNMS.getNative(stack).hasName(); } /** * Gets the current display name of an Item. * If no name is set, the default Material name is returned instead. * * @param stack to get the display name of * @return display name */ public static String getDisplayName(org.bukkit.inventory.ItemStack stack) { return CommonNMS.getNative(stack).getName(); } /** * Sets the current display name of an Item * * @param stack to set the display name of * @param displayName to set to, null to reset to the default */ public static void setDisplayName(org.bukkit.inventory.ItemStack stack, String displayName) { if (displayName != null) { CommonNMS.getNative(stack).c(displayName); } else if (hasDisplayName(stack)) { CommonNMS.getNative(stack).tag.remove("display"); } } }