package net.scapeemulator.game.model.player.inventory; import java.util.ArrayList; import java.util.List; import net.scapeemulator.cache.def.ItemDefinition; import net.scapeemulator.game.model.World; import net.scapeemulator.game.model.definition.ItemDefinitions; import net.scapeemulator.game.model.mob.Mob; import net.scapeemulator.game.model.player.Item; import net.scapeemulator.game.model.player.Player; import net.scapeemulator.game.model.player.SlottedItem; import net.scapeemulator.game.util.math.BasicMath; public final class Inventory { public enum StackMode { ALWAYS, STACKABLE_ONLY; } private final Player player; private final StackMode stackMode; private final Item[] items; private final boolean weighted; private final List<InventoryListener> listeners = new ArrayList<>(); /** * The total weight of this inventory in grams. */ private int weight; /** * If the inventory is locked, the items cannot be modified in any way. */ private boolean locked; /** * If the inventory is silent, the listeners are not called. */ private boolean silent; public Inventory(Player player, int slots) { this(player, slots, StackMode.STACKABLE_ONLY, true); } public Inventory(Player player, int slots, StackMode stackMode, boolean weighted) { this.player = player; this.stackMode = stackMode; this.items = new Item[slots]; this.weighted = weighted; } public Inventory(Inventory inventory) { this.stackMode = inventory.stackMode; this.items = inventory.toArray(); this.player = inventory.player; this.weight = inventory.weight; this.weighted = inventory.weighted; } public Item[] toArray() { Item[] array = new Item[items.length]; System.arraycopy(items, 0, array, 0, items.length); return array; } public void addListener(InventoryListener listener) { listeners.add(listener); } public void removeListener(InventoryListener listener) { listeners.remove(listener); } public void removeListeners() { listeners.clear(); } public void refresh() { fireItemsChanged(); } public Item get(int slot) { checkSlot(slot); return items[slot]; } /** * Sets the item at the specified slot to the specified item. * * @param slot the slot id to set * @param item the item to set * @return the item that was in the slot before changing it, if any */ public Item set(int slot, Item item) { if (locked) { return null; } checkSlot(slot); if (weighted) { if (items[slot] != null) { weight -= items[slot].getDefinition().getWeight(); } if (item != null) { weight += item.getDefinition().getWeight(); } } Item old = items[slot]; items[slot] = item; fireItemChanged(slot, old); return old; } /** * Swaps the position of two items in the inventory. * * @param slot1 original slot * @param slot2 destination slot */ public void swap(int slot1, int slot2) { if (locked) { return; } checkSlot(slot1); checkSlot(slot2); Item tmp = items[slot1]; items[slot1] = items[slot2]; items[slot2] = tmp; fireItemChanged(slot1, items[slot2]); fireItemChanged(slot2, items[slot1]); } public Item add(Item item) { return add(item, -1); } public Item add(Item item, int preferredSlot) { if (locked) { return item; } int id = item.getId(); boolean stackable = isStackable(item); if (stackable) { /* try to add this item to an existing stack */ int slot = slotOf(id); if (slot != -1) { Item other = items[slot]; int amount; /* check if there are too many items in the stack */ int overflow = BasicMath.integerOverflow(other.getAmount(), item.getAmount()); Item remaining = null; if (overflow != 0) { amount = Integer.MAX_VALUE; remaining = new Item(id, overflow); fireCapacityExceeded(); } else { amount = other.getAmount() + item.getAmount(); } /* update stack and return any remaining items */ set(slot, new Item(item.getId(), amount)); return remaining; } /* try to add this item to the preferred slot */ if (preferredSlot != -1) { checkSlot(preferredSlot); if (items[preferredSlot] == null) { set(preferredSlot, item); return null; } } /* try to add this item to any slot */ for (slot = 0; slot < items.length; slot++) { if (items[slot] == null) { set(slot, item); return null; } } /* give up */ fireCapacityExceeded(); return item; } else { final Item single = new Item(id, 1); int remaining = item.getAmount(); if (remaining == 0) return null; /* try to first place item at the preferred slot */ if (preferredSlot != -1) { checkSlot(preferredSlot); if (items[preferredSlot] == null) { set(preferredSlot, single); remaining--; } } if (remaining == 0) return null; /* place any subsequent remaining items wherever space is available */ for (int slot = 0; slot < items.length; slot++) { if (items[slot] == null) { set(slot, single); remaining--; } if (remaining == 0) return null; } /* give up */ fireCapacityExceeded(); return new Item(id, remaining); } } public Item remove(SlottedItem item) { return remove(item.getItem(), item.getSlot()); } public Item remove(Item item) { return remove(item, -1); } /** * @param item * @param preferredSlot * @return Item with amount value being the number actually removed */ public Item remove(Item item, int preferredSlot) { if (locked) { return null; } int id = item.getId(); boolean stackable = isStackable(item); if (stackable) { /* try to remove this item from its stack */ int slot = slotOf(id); if (slot != -1) { Item other = items[slot]; if (other.getAmount() <= item.getAmount()) { set(slot, null); return new Item(id, other.getAmount()); } else { other = new Item(id, other.getAmount() - item.getAmount()); set(slot, other); return item; } } return null; } else { int removed = 0; /* try to remove the item from the preferred slot first */ if (preferredSlot != -1) { checkSlot(preferredSlot); if (items[preferredSlot] != null && items[preferredSlot].getId() == id) { set(preferredSlot, null); if (++removed >= item.getAmount()) return new Item(id, removed); } } /* try other slots */ for (int slot = 0; slot < items.length; slot++) { Item other = items[slot]; if (other != null && other.getId() == id) { set(slot, null); if (++removed >= item.getAmount()) return new Item(id, removed); } } return removed == 0 ? null : new Item(id, removed); } } public void shift() { if (locked) { return; } int destSlot = 0; for (int slot = 0; slot < items.length; slot++) { Item item = items[slot]; if (item != null) { items[destSlot++] = item; } } for (int slot = destSlot; slot < items.length; slot++) items[slot] = null; fireItemsChanged(); } public void empty() { if (locked) { return; } for (int slot = 0; slot < items.length; slot++) items[slot] = null; fireItemsChanged(); } public boolean isEmpty() { for (int slot = 0; slot < items.length; slot++) if (items[slot] != null) return false; return true; } public int freeSlot() { for (int slot = 0; slot < items.length; slot++) { if (items[slot] == null) { return slot; } } return -1; } public int freeSlots() { int slots = 0; for (int slot = 0; slot < items.length; slot++) if (items[slot] == null) slots++; return slots; } public int slotOf(int id) { for (int slot = 0; slot < items.length; slot++) { Item item = items[slot]; if (item != null && item.getId() == id) return slot; } return -1; } public int getAmount(int id) { int amount = 0; for (Item item : items) { if (item == null) continue; if (item.getId() == id) { amount += item.getAmount(); } } return amount; } public int getAmountNotedAndUnnoted(int id) { ItemDefinition def = ItemDefinitions.forId(id); int unnoted = def.getUnnotedItemId(); int noted = def.getNotedItemId(); if (unnoted != noted) { return getAmount(unnoted) + getAmount(noted); } return getAmount(id); } public boolean contains(int id) { return slotOf(id) != -1; } public boolean contains(int id, int amount) { return getAmount(id) >= amount; } public boolean contains(Item item) { return getAmount(item.getId()) >= item.getAmount(); } /** * Removes all given items from this inventory. * * @param items the items to remove * @return a collection of items that were NOT removed, empty means the operation completed * successfully */ public List<Item> removeAll(Item... items) { List<Item> notRemoved = new ArrayList<>(); for (Item toRemove : items) { if (toRemove != null) { Item removed = remove(toRemove); if (!removed.equals(toRemove)) { notRemoved.add(new Item(toRemove.getId(), toRemove.getAmount() - removed.getAmount())); } } } return notRemoved; } /** * Adds all given items to this inventory. * * @param items the items to add * @return a collection of items that were NOT added, empty means the operation completed * successfully */ public List<Item> addAll(Item... items) { List<Item> notAdded = new ArrayList<>(); for (Item toAdd : items) { if (toAdd != null) { Item overflow = add(toAdd); if (overflow != null) { notAdded.add(overflow); } } } return notAdded; } private void fireItemChanged(int slot, Item oldItem) { if (!silent) { for (InventoryListener listener : listeners) listener.itemChanged(this, slot, items[slot], oldItem); } } private void fireItemsChanged() { if (weighted) { weight = 0; for (Item item : items) { if (item != null) { weight += item.getDefinition().getWeight(); } } } if (!silent) { for (InventoryListener listener : listeners) listener.itemsChanged(this); } } public void fireCapacityExceeded() { if (!silent) { for (InventoryListener listener : listeners) listener.capacityExceeded(this); } } private boolean isStackable(Item item) { if (stackMode == StackMode.ALWAYS) return true; return item.getDefinition().isStackable(); } public boolean verify(int slot, int itemId) { return get(slot) != null && get(slot).getId() == itemId; } public void dropAll(Mob receiver) { for (Item item : items) { if (item != null) { World.getWorld().getGroundItems().add(item.getId(), item.getAmount(), player.getPosition(), receiver instanceof Player ? (Player) receiver : player); } } for (int slot = 0; slot < items.length; slot++) items[slot] = null; fireItemsChanged(); } public void checkSlot(int slot) { if (slot < 0 || slot >= items.length) throw new IndexOutOfBoundsException("slot out of range: " + slot); } public int getWeight() { return weight; } public void silence() { silent = true; } public boolean locked() { return locked; } public void unsilence() { silent = false; } public void lock() { locked = true; } public void unlock() { locked = false; } }