package com.supaham.commons.bukkit.utils; import static com.google.common.base.Preconditions.checkNotNull; import com.google.common.base.Optional; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Iterables; import com.supaham.commons.utils.ArrayUtils; import org.bukkit.Material; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import java.util.Collection; import java.util.Collections; import java.util.LinkedHashMap; import java.util.Map; import javax.annotation.Nonnull; /** * Utility methods for working with {@link Inventory} instances. This class contains methods such * as * {@link #removeMaterial(Inventory, Material, boolean, int)}, and more. * * @since 0.1 */ public class InventoryUtils { public static InventoryModificationResult addItem(@Nonnull Inventory inventory, @Nonnull ItemStack item, int... slots) { checkNotNull(inventory, "inventory cannot be null."); checkNotNull(item, "item cannot be null."); item = item.clone(); boolean anySlot = false; // If we should add to any and all available slots, just let bukkit handle it for (int slot : slots) { if (slot == -1) { anySlot = true; } } InventoryModificationResult result = new InventoryModificationResult(); outer: for (int slot = 0; slot < inventory.getSize(); slot++) { if (!anySlot) { for (int i : slots) { if (slot != i) { continue outer; } } } // TODO should the success inserts insert the exact increase to said slot instead? ItemStack currItem = inventory.getItem(slot); if (!ItemUtils.isAir(currItem)) { int diff = currItem.getMaxStackSize() - currItem.getAmount(); // how much we can add to this itemstack if (diff > 0 && item.isSimilar(currItem)) { // we can add to this stack if (diff >= item.getAmount()) { // currItem can handle the whole 'item' itemstack currItem.setAmount(currItem.getAmount() + item.getAmount()); item.setAmount(0); result.success.put(slot, currItem.clone()); inventory.setItem(slot, currItem); // Update the itemstack in the inventory break; // The given item stack is depleted. Mission accomplished. } else { item.setAmount(item.getAmount() - diff); currItem.setAmount(currItem.getAmount() + diff); result.success.put(slot, currItem.clone()); inventory.setItem(slot, currItem); // Update the itemstack in the inventory } } } else { inventory.setItem(slot, item); // add the given itemstack to the inventory result.success.put(slot, item); } } if (item.getAmount() > 0) { result.failure.put(-1, item); } return result; } /** * Deducts a specific amount of {@link Material} from an {@link Inventory}. This is equivalent to * calling {@link #removeMaterial(Inventory, Material, boolean, int)} with the boolean as false. * * @param inventory inventory to deduct from * @param type material to remove * @param amount amount of the material to deduct * * @return count of items that were removed from the inventory */ public static int removeMaterial(@Nonnull Inventory inventory, @Nonnull Material type, int amount) { return removeMaterial(inventory, type, false, amount); } /** * Deducts a specific amount of {@link Material} from an {@link Inventory}. * * @param inventory inventory to deduct from * @param type material to remove * @param singleStack whether to stop at one itemstack or keep going * @param amount amount of the material to deduct * * @return count of items that were removed from the inventory */ public static int removeMaterial(@Nonnull Inventory inventory, @Nonnull Material type, boolean singleStack, int amount) { checkNotNull(inventory, "inventory cannot be null."); checkNotNull(type, "material cannot be null."); if (amount <= 0) { return 0; } int initialAmount = amount; if (inventory instanceof PlayerInventory) { int heldItemSlot = ((PlayerInventory) inventory).getHeldItemSlot(); ItemStack held = inventory.getItem(heldItemSlot); if (held != null && held.getType() == type) { int newAmount = held.getAmount() - amount; // new amount of the held itemstack amount -= held.getAmount(); if (newAmount > 0) { // The itemstack is only to be deducted, not removed (check else) held.setAmount(newAmount); return initialAmount - amount; } else { // The held itemstack is depleted, remove it. inventory.setItem(heldItemSlot, null); // check if we should keep removing items. if (singleStack || amount <= 0) { return initialAmount - amount; } } } } ItemStack[] contents = inventory.getContents(); // Try deduct from the held item first if (inventory instanceof PlayerInventory) { int heldItemSlot = ((PlayerInventory) inventory).getHeldItemSlot(); ItemStack[] held = new ItemStack[]{inventory.getItem(heldItemSlot)}; contents = (ItemStack[]) ArrayUtils.addAll(held, contents); } for (int slot = 0; slot < contents.length; slot++) { ItemStack is = contents[slot]; if (is != null && is.getType() == type) { int newAmount = is.getAmount() - amount; // new amount of the held itemstack amount -= is.getAmount(); if (newAmount > 0) { // The itemstack is only to be deducted, not removed (check else) is.setAmount(newAmount); break; } else { // The itemstack is depleted, remove it. inventory.setItem(slot, null); // check if we should keep removing items. if (singleStack || amount <= 0) { break; } } } } return initialAmount - amount; } public static class InventoryModificationResult { private final Map<Integer, ItemStack> success = new LinkedHashMap<>(); private final Map<Integer, ItemStack> failure = new LinkedHashMap<>(); @Override public String toString() { return "InventoryModificationResult{" + "success=" + success + ", failure=" + failure + '}'; } public Map<Integer, ItemStack> getSuccesses() { return ImmutableMap.copyOf(success); } public Map<Integer, ItemStack> getFailures() { return ImmutableMap.copyOf(failure); } public Collection<ItemStack> getSuccessItems() { return Collections.unmodifiableCollection(success.values()); } public Optional<ItemStack> getFirstSuccess() { return Optional.fromNullable(Iterables.getFirst(success.values(), null)); } public Optional<ItemStack> getLastSuccess() { return Optional.fromNullable(Iterables.getLast(success.values(), null)); } public Collection<ItemStack> getFailureItems() { return Collections.unmodifiableCollection(failure.values()); } public Optional<ItemStack> getFirstFailure() { return Optional.fromNullable(Iterables.getFirst(failure.values(), null)); } public Optional<ItemStack> getLastFailure() { return Optional.fromNullable(Iterables.getLast(failure.values(), null)); } } }