package net.aufdemrand.denizen.objects; import net.aufdemrand.denizen.BukkitScriptEntryData; import net.aufdemrand.denizen.nms.NMSHandler; import net.aufdemrand.denizen.nms.NMSVersion; import net.aufdemrand.denizen.nms.abstracts.ImprovedOfflinePlayer; import net.aufdemrand.denizen.objects.notable.NotableManager; import net.aufdemrand.denizen.scripts.containers.core.InventoryScriptContainer; import net.aufdemrand.denizen.scripts.containers.core.InventoryScriptHelper; import net.aufdemrand.denizen.tags.BukkitTagContext; import net.aufdemrand.denizen.utilities.Utilities; import net.aufdemrand.denizen.utilities.debugging.dB; import net.aufdemrand.denizen.utilities.depends.Depends; import net.aufdemrand.denizencore.objects.*; import net.aufdemrand.denizencore.objects.aH.Argument; import net.aufdemrand.denizencore.objects.aH.PrimitiveType; import net.aufdemrand.denizencore.objects.notable.Notable; import net.aufdemrand.denizencore.objects.notable.Note; import net.aufdemrand.denizencore.objects.properties.Property; import net.aufdemrand.denizencore.objects.properties.PropertyParser; import net.aufdemrand.denizencore.scripts.ScriptRegistry; import net.aufdemrand.denizencore.tags.Attribute; import net.aufdemrand.denizencore.tags.TagContext; import net.aufdemrand.denizencore.utilities.CoreUtilities; import net.citizensnpcs.api.CitizensAPI; import org.bukkit.Bukkit; import org.bukkit.Material; import org.bukkit.block.BlockState; import org.bukkit.block.DoubleChest; import org.bukkit.entity.Entity; import org.bukkit.entity.Player; import org.bukkit.event.inventory.InventoryType; import org.bukkit.inventory.CraftingInventory; import org.bukkit.inventory.HorseInventory; import org.bukkit.inventory.Inventory; import org.bukkit.inventory.InventoryHolder; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.PlayerInventory; import org.bukkit.inventory.meta.BookMeta; import java.util.*; import java.util.regex.Matcher; import java.util.regex.Pattern; public class dInventory implements dObject, Notable, Adjustable { public static dInventory mirrorBukkitInventory(Inventory inventory) { // Scripts have priority over notables if (InventoryScriptHelper.tempInventoryScripts.containsKey(inventory)) { return new dInventory(inventory).setIdentifiers("script", InventoryScriptHelper.tempInventoryScripts.get(inventory)); } // Use the map to get notable inventories if (InventoryScriptHelper.notableInventories.containsKey(inventory.getTitle())) { return InventoryScriptHelper.notableInventories.get(inventory.getTitle()); } // Iterate through offline player inventories for (Map.Entry<UUID, PlayerInventory> inv : ImprovedOfflinePlayer.offlineInventories.entrySet()) { if (inv.getValue().equals(inventory)) { return new dInventory(NMSHandler.getInstance().getPlayerHelper().getOfflineData(inv.getKey())); } } // Iterate through offline player enderchests for (Map.Entry<UUID, Inventory> inv : ImprovedOfflinePlayer.offlineEnderChests.entrySet()) { if (inv.getValue().equals(inventory)) { return new dInventory(NMSHandler.getInstance().getPlayerHelper().getOfflineData(inv.getKey()), true); } } return new dInventory(inventory); } ///////////////////// // PATTERNS ///////////////// final static Pattern inventory_by_type = Pattern.compile("(in@)(npc|player|enderchest|workbench|entity|location|generic)\\[(.+?)\\]", Pattern.CASE_INSENSITIVE); final static Pattern inventory_by_script = Pattern.compile("(in@)(.+)", Pattern.CASE_INSENSITIVE); ///////////////////// // STATIC FIELDS ///////////////// // The maximum number of slots a Bukkit inventory can have public final static int maxSlots = 54; // All of the inventory id types we use public final static String[] idTypes = {"npc", "player", "enderchest", "workbench", "entity", "location", "generic"}; ///////////////////// // NOTABLE METHODS ///////////////// public boolean isUnique() { return NotableManager.isSaved(this); } @Note("Inventories") public String getSaveObject() { return "in@" + idType + PropertyParser.getPropertiesString(this); } public void makeUnique(String id) { String title = inventory.getTitle(); if (title == null || title.startsWith("container.")) { title = inventory.getType().getDefaultTitle(); } // You can only have 32 characters in an inventory title... So let's make sure we have at least 3 colors... // which brings notable inventory title lengths down to 26... TODO: document this/fix if possible in later version if (title.length() > 26) { title = title.substring(0, title.charAt(25) == '§' ? 25 : 26); } String colors; while (true) { colors = Utilities.generateRandomColors(3); if (!InventoryScriptHelper.notableInventories.containsKey(title + colors)) { ItemStack[] contents = inventory.getContents(); if (getInventoryType() == InventoryType.CHEST) { inventory = Bukkit.getServer().createInventory(null, inventory.getSize(), title + colors); } else { inventory = Bukkit.getServer().createInventory(null, inventory.getType(), title + colors); } inventory.setContents(contents); InventoryScriptHelper.notableInventories.put(title + colors, this); break; } } idType = null; idHolder = null; loadIdentifiers(); NotableManager.saveAs(this, id); } public void forget() { NotableManager.remove(idHolder); } ////////////////// // OBJECT FETCHER //////////////// @Fetchable("in") public static dInventory valueOf(String string, TagContext context) { if (context == null) { return valueOf(string, null, null); } else { return valueOf(string, ((BukkitTagContext) context).player, ((BukkitTagContext) context).npc); } } /** * Gets a dInventory from a string format. * * @param string The inventory in string form. (in@player[playerName], in@scriptName, etc.) * @return The dInventory value. If the string is incorrectly formatted or * the specified inventory is invalid, this is null. */ public static dInventory valueOf(String string, dPlayer player, dNPC npc) { if (string == null) { return null; } /////// // Handle objects with properties through the object fetcher Matcher m = ObjectFetcher.DESCRIBED_PATTERN.matcher(string); if (m.matches()) { return ObjectFetcher.getObjectFrom(dInventory.class, string, new BukkitTagContext(player, npc, false, null, false, null)); } // Match in@scriptName for Inventory Scripts, as well as in@notableName m = inventory_by_script.matcher(string); if (m.matches()) { if (ScriptRegistry.containsScript(m.group(2), InventoryScriptContainer.class)) { return ScriptRegistry.getScriptContainerAs(m.group(2), InventoryScriptContainer.class) .getInventoryFrom(player, npc); } if (NotableManager.isSaved(m.group(2)) && NotableManager.isType(m.group(2), dInventory.class)) { return (dInventory) NotableManager.getSavedObject(m.group(2)); } for (String idType : idTypes) { if (m.group(2).equalsIgnoreCase(idType)) { return new dInventory(m.group(2)); } } } m = inventory_by_type.matcher(string); if (m.matches()) { // Set the type of the inventory holder String type = CoreUtilities.toLowerCase(m.group(2)); // Set the name/id/location of the inventory holder String holder = m.group(3); if (type.equals("generic")) { Argument arg = Argument.valueOf(holder); if (arg.matchesEnum(InventoryType.values())) { return new dInventory(InventoryType.valueOf(holder.toUpperCase())); } else if (arg.matchesPrimitive(PrimitiveType.Integer)) { return new dInventory(arg.asElement().asInt()); } else { dB.echoError("That type of inventory does not exist!"); } } else if (type.equals("npc")) { if (dNPC.matches(holder)) { return dNPC.valueOf(holder).getDenizenInventory(); } } else if (type.equals("player")) { if (dPlayer.matches(holder)) { return dPlayer.valueOf(holder).getInventory(); } } else if (type.equals("workbench")) { if (dPlayer.matches(holder)) { dInventory workbench = dPlayer.valueOf(holder).getWorkbench(); if (workbench != null) { dB.echoError("Value of dInventory returning null (" + string + ")." + " Specified player does not have an open workbench."); } else { return workbench; } } } else if (type.equals("enderchest")) { if (dPlayer.matches(holder)) { return dPlayer.valueOf(holder).getEnderChest(); } } else if (type.equals("entity")) { if (dEntity.matches(holder)) { return dEntity.valueOf(holder).getInventory(); } } else if (type.equals("location")) { if (dLocation.matches(holder)) { return dLocation.valueOf(holder).getInventory(); } } // If the dInventory is invalid, alert the user and return null dB.echoError("Value of dInventory returning null. Invalid " + type + " specified: " + holder); return null; } dB.echoError("Value of dInventory returning null. Invalid dInventory specified: " + string); return null; } /** * Determine whether a string is a valid inventory. * * @param arg the arg string * @return true if matched, otherwise false */ public static boolean matches(String arg) { // Every single dInventory should have the in@ prefix. No exceptions. return CoreUtilities.toLowerCase(arg).startsWith("in@"); } /////////////// // Constructors ///////////// String idType = null; String idHolder = null; public dInventory(Inventory inventory) { this.inventory = inventory; loadIdentifiers(); } public dInventory(Inventory inventory, InventoryHolder holder) { this.inventory = inventory; loadIdentifiers(holder); } public dInventory(InventoryHolder holder) { inventory = holder.getInventory(); loadIdentifiers(); } public dInventory(ItemStack[] items) { inventory = Bukkit.getServer().createInventory(null, (int) Math.ceil(items.length / 9) * 9); setContents(items); loadIdentifiers(); } public dInventory(ImprovedOfflinePlayer offlinePlayer) { this(offlinePlayer, false); } public dInventory(ImprovedOfflinePlayer offlinePlayer, boolean isEnderChest) { inventory = isEnderChest ? offlinePlayer.getEnderChest() : offlinePlayer.getInventory(); setIdentifiers(isEnderChest ? "enderchest" : "player", "p@" + offlinePlayer.getUniqueId()); } public dInventory(int size, String title) { if (size <= 0 || size % 9 != 0) { dB.echoError("InventorySize must be multiple of 9, and greater than 0."); return; } inventory = Bukkit.getServer().createInventory(null, size, title); loadIdentifiers(); } public dInventory(InventoryType type) { inventory = Bukkit.getServer().createInventory(null, type); loadIdentifiers(); } public dInventory(int size) { this(size, "Chest"); } public dInventory(String idType) { this.idType = CoreUtilities.toLowerCase(idType); for (Mechanism mechanism : mechanisms) { adjust(mechanism); } mechanisms.clear(); } ///////////////////// // INSTANCE FIELDS/METHODS ///////////////// // Associated with Bukkit Inventory private Inventory inventory = null; public Inventory getInventory() { return inventory; } /** * Changes the inventory to a new inventory, possibly of a different * type, size, and with different contents. * NOTE: SHOULD ONLY BE USED IN CASES WHERE THERE * ARE NO OTHER OPTIONS. * * @param inventory The new inventory */ public void setInventory(Inventory inventory) { this.inventory = inventory; loadIdentifiers(); } public void setInventory(Inventory inventory, dPlayer player) { this.inventory = inventory; this.idHolder = player.identify(); } public void setTitle(String title) { if (!(getIdType().equals("generic") || getIdType().equals("script")) || title == null) { return; } else if (inventory == null) { inventory = Bukkit.getServer().createInventory(null, maxSlots, title); loadIdentifiers(); return; } ItemStack[] contents = inventory.getContents(); if (inventory.getType() == InventoryType.CHEST) { inventory = Bukkit.getServer().createInventory(null, inventory.getSize(), title); } else { inventory = Bukkit.getServer().createInventory(null, inventory.getType(), title); } inventory.setContents(contents); loadIdentifiers(); } public boolean containsItem(dItem item, int amount) { if (item == null) { return false; } item = new dItem(item.getItemStack().clone()); item.setAmount(1); String myItem = CoreUtilities.toLowerCase(item.getFullString()); for (int i = 0; i < inventory.getSize(); i++) { ItemStack is = inventory.getItem(i); if (is == null) { continue; } is = is.clone(); int count = is.getAmount(); is.setAmount(1); String newItem = CoreUtilities.toLowerCase(new dItem(is).getFullString()); if (myItem.equals(newItem)) { if (count <= amount) { amount -= count; if (amount == 0) { return true; } } else if (count > amount) { return true; } } } return false; } public boolean removeItem(dItem item, int amount) { if (item == null) { return false; } item.setAmount(1); String myItem = CoreUtilities.toLowerCase(item.getFullString()); for (int i = 0; i < inventory.getSize(); i++) { ItemStack is = inventory.getItem(i); if (is == null) { continue; } is = is.clone(); int count = is.getAmount(); is.setAmount(1); // Note: this double-parsing is intentional, as part of a hotfix for a larger issue String newItem = CoreUtilities.toLowerCase(dItem.valueOf(new dItem(is).getFullString()).getFullString()); if (myItem.equals(newItem)) { if (count <= amount) { inventory.setItem(i, null); amount -= count; if (amount == 0) { return true; } } else if (count > amount) { is.setAmount(count - amount); inventory.setItem(i, is); return true; } } } return false; } public void setSize(int size) { if (!getIdType().equals("generic")) { return; } else if (size <= 0 || size % 9 != 0) { dB.echoError("InventorySize must be multiple of 9, and greater than 0."); return; } else if (inventory == null) { inventory = Bukkit.getServer().createInventory(null, size, "Chest"); loadIdentifiers(); return; } int oldSize = inventory.getSize(); ItemStack[] oldContents = inventory.getContents(); ItemStack[] newContents = new ItemStack[size]; if (oldSize > size) { for (int i = 0; i < size; i++) // TODO: Why is this a manual copy? { newContents[i] = oldContents[i]; } } else { newContents = oldContents; } String title = inventory.getTitle(); inventory = Bukkit.getServer().createInventory(null, size, (title != null ? title : inventory.getType().getDefaultTitle())); inventory.setContents(newContents); loadIdentifiers(); } private void loadIdentifiers() { loadIdentifiers(inventory.getHolder()); } private void loadIdentifiers(final InventoryHolder holder) { if (inventory == null) { return; } if (holder != null) { if (holder instanceof dNPC) { idType = "npc"; idHolder = ((dNPC) holder).identify(); return; } else if (holder instanceof Player) { if (Depends.citizens != null && CitizensAPI.getNPCRegistry().isNPC((Player) holder)) { idType = "npc"; idHolder = (dNPC.fromEntity((Player) holder)).identify(); return; } if (inventory.getType() == InventoryType.ENDER_CHEST) { idType = "enderchest"; } else if (inventory.getType() == InventoryType.WORKBENCH) { idType = "workbench"; } else { idType = "player"; } idHolder = new dPlayer((Player) holder).identify(); return; } else if (holder instanceof Entity) { idType = "entity"; idHolder = new dEntity((Entity) holder).identify(); return; } else { idType = "location"; try { idHolder = getLocation(holder).identify(); } catch (NullPointerException e) { idHolder = "null"; } return; } } else if (getIdType().equals("player")) { // Iterate through offline player inventories for (Map.Entry<UUID, PlayerInventory> inv : ImprovedOfflinePlayer.offlineInventories.entrySet()) { if (inv.getValue().equals(inventory)) { idHolder = new dPlayer(inv.getKey()).identify(); return; } } } else if (getIdType().equals("enderchest")) { // Iterate through offline player enderchests for (Map.Entry<UUID, Inventory> inv : ImprovedOfflinePlayer.offlineEnderChests.entrySet()) { if (inv.getValue().equals(inventory)) { idHolder = new dPlayer(inv.getKey()).identify(); return; } } } else if (getIdType().equals("script")) { if (InventoryScriptHelper.tempInventoryScripts.containsKey(inventory)) { idHolder = InventoryScriptHelper.tempInventoryScripts.get(inventory); return; } } idType = "generic"; idHolder = getInventory().getType().name(); } public dInventory setIdentifiers(String type, String holder) { idType = type; idHolder = holder; return this; } public String getIdType() { return idType == null ? "" : idType; } public String getIdHolder() { return idHolder == null ? "" : idHolder; } /** * Return the dLocation of this inventory's * holder * * @return The holder's dLocation */ public dLocation getLocation() { return getLocation(inventory.getHolder()); } public dLocation getLocation(InventoryHolder holder) { if (inventory != null && holder != null) { if (holder instanceof BlockState) { return new dLocation(((BlockState) holder).getLocation()); } else if (holder instanceof DoubleChest) { return new dLocation(((DoubleChest) holder).getLocation()); } else if (holder instanceof Entity) { return new dLocation(((Entity) holder).getLocation()); } else if (holder instanceof dNPC) { dNPC npc = (dNPC) holder; if (npc.getLocation() == null) { return new dLocation(((dNPC) holder).getCitizen().getStoredLocation()); } return npc.getLocation(); } } return null; } public ItemStack[] getContents() { if (inventory != null) { return inventory.getContents(); } else { return new ItemStack[0]; } } public ItemStack[] getStorageContents() { if (inventory != null) { return NMSHandler.getVersion().isAtLeast(NMSVersion.v1_9_R2) ? inventory.getStorageContents() : inventory.getContents(); } else { return new ItemStack[0]; } } public dList getEquipment() { ItemStack[] equipment = null; if (inventory instanceof PlayerInventory) { equipment = ((PlayerInventory) inventory).getArmorContents(); } else if (inventory instanceof HorseInventory) { equipment = new ItemStack[]{((HorseInventory) inventory).getSaddle(), ((HorseInventory) inventory).getArmor()}; } if (equipment == null) { return null; } dList equipmentList = new dList(); for (ItemStack item : equipment) { equipmentList.add(new dItem(item).identify()); } return equipmentList; } public InventoryType getInventoryType() { return inventory.getType(); } public int getSize() { return inventory.getSize(); } public void remove(ItemStack item) { inventory.remove(item); } public void setContents(ItemStack[] contents) { inventory.setContents(contents); } public void setContents(dList list) { int size; if (inventory == null) { size = (int) Math.ceil(list.size() / 9) * 9; if (size == 0) { size = 9; } inventory = Bukkit.getServer().createInventory(null, size); loadIdentifiers(); } else { size = inventory.getSize(); } ItemStack[] contents = new ItemStack[size]; int filled = 0; for (dItem item : list.filter(dItem.class)) { contents[filled] = item.getItemStack(); filled++; } final ItemStack air = new ItemStack(Material.AIR); while (filled < size) { contents[filled] = air; filled++; } inventory.setContents(contents); if (Depends.citizens != null && dNPC.matches(idHolder)) { // TODO: Directly store holder dNPC.valueOf(idHolder).getInventoryTrait().setContents(contents); } } public boolean update() { if (getIdType().equals("player")) { dPlayer.valueOf(idHolder).getPlayerEntity().updateInventory(); return true; } return false; } public int firstPartial(int startSlot, ItemStack item) { ItemStack[] inventory = getContents(); if (item == null) { return -1; } for (int i = startSlot; i < inventory.length; i++) { ItemStack item1 = inventory[i]; if (item1 != null && item1.getAmount() < item.getMaxStackSize() && item1.isSimilar(item)) { return i; } } return -1; } public int firstEmpty(int startSlot) { ItemStack[] inventory = getContents(); for (int i = startSlot; i < inventory.length; i++) { if (inventory[i] == null) { return i; } } return -1; } ///////////////////// // INVENTORY MANIPULATION ///////////////// // Somewhat simplified version of CraftBukkit's current code public dInventory add(int slot, ItemStack... items) { if (inventory == null || items == null) { return this; } for (int i = 0; i < items.length; i++) { ItemStack item = items[i]; if (item == null) { continue; } int amount = item.getAmount(); int max = item.getMaxStackSize(); while (true) { // Do we already have a stack of it? int firstPartial = firstPartial(slot, item); // Drat! no partial stack if (firstPartial == -1) { // Find a free spot! int firstFree = firstEmpty(slot); if (firstFree == -1) { // No space at all! break; } else { // More than a single stack! if (amount > max) { ItemStack clone = item.clone(); clone.setAmount(max); inventory.setItem(firstFree, clone); item.setAmount(amount -= max); } else { // Just store it inventory.setItem(firstFree, item); break; } } } else { // So, apparently it might only partially fit, well lets do just that ItemStack partialItem = inventory.getItem(firstPartial); int partialAmount = partialItem.getAmount(); int total = amount + partialAmount; // Check if it fully fits if (total <= max) { partialItem.setAmount(total); break; } // It fits partially partialItem.setAmount(max); item.setAmount(amount = total - max); } } } return this; } public List<ItemStack> addWithLeftovers(int slot, boolean keepMaxStackSize, ItemStack... items) { if (inventory == null || items == null) { return null; } List<ItemStack> leftovers = new ArrayList<ItemStack>(); for (int i = 0; i < items.length; i++) { ItemStack item = items[i]; if (item == null) { continue; } int amount = item.getAmount(); int max; if (keepMaxStackSize) { max = item.getMaxStackSize(); } else { max = 64; } while (true) { // Do we already have a stack of it? int firstPartial = firstPartial(slot, item); // Drat! no partial stack if (firstPartial == -1) { // Find a free spot! int firstFree = firstEmpty(slot); if (firstFree == -1) { // No space at all! leftovers.add(item); break; } else { // More than a single stack! if (amount > max) { ItemStack clone = item.clone(); clone.setAmount(max); inventory.setItem(firstFree, clone); item.setAmount(amount -= max); } else { // Just store it inventory.setItem(firstFree, item); break; } } } else { // So, apparently it might only partially fit, well lets do just that ItemStack partialItem = inventory.getItem(firstPartial); int partialAmount = partialItem.getAmount(); int total = amount + partialAmount; // Check if it fully fits if (total <= max) { partialItem.setAmount(total); break; } // It fits partially partialItem.setAmount(max); item.setAmount(amount = total - max); } } } return leftovers; } public List<ItemStack> setWithLeftovers(int slot, ItemStack... items) { if (inventory == null || items == null) { return null; } List<ItemStack> leftovers = new ArrayList<ItemStack>(); for (int i = 0; i < items.length; i++) { ItemStack item = items[i]; try { inventory.setItem(i + slot, item); } catch (Exception e) { leftovers.add(i + slot, item); } } return leftovers; } /** * Count the number or quantities of stacks that * match an item in an inventory. * * @param item The item (can be null) * @param stacks Whether stacks should be counted * instead of item quantities * @return The number of stacks or quantity of items */ public int count(ItemStack item, boolean stacks) { if (inventory == null) { return 0; } int qty = 0; for (ItemStack invStack : inventory) { // If ItemStacks are empty here, they are null if (invStack != null) { // If item is null, include all items in the // inventory if (item == null || invStack.isSimilar(item)) { // If stacks is true, only count the number // of stacks // // Otherwise, count the quantities of stacks if (stacks) { qty++; } else { qty = qty + invStack.getAmount(); } } } } return qty; } /** * Keep only the items from a certain array * in this inventory, removing all others * * @param items The array of items * @return The resulting dInventory */ public dInventory keep(ItemStack[] items) { if (inventory == null || items == null) { return this; } for (ItemStack invStack : inventory) { if (invStack != null) { boolean keep = false; // See if the item array contains // this inventory item for (ItemStack item : items) { if (invStack.isSimilar(item)) { keep = true; break; } } // If the item array did not contain // this inventory item, remove it // from the inventory if (!keep) { this.remove(invStack); } } } return this; } /** * Exclude an array of items from this * inventory by removing them over and over * until they are completely gone * * @param items The array of items * @return The resulting dInventory */ public dInventory exclude(ItemStack[] items) { if (inventory == null || items == null) { return this; } int oldCount = this.count(null, false); int newCount = -1; while (oldCount != newCount) { oldCount = newCount; newCount = this.remove(items).count(null, false); } return this; } /** * Fill an inventory with an array of items by * continuing to add the items to it over and * over until there is no more room * * @param items The array of items * @return The resulting dInventory */ public dInventory fill(ItemStack[] items) { if (inventory == null || items == null) { return this; } int oldCount = this.count(null, false); int newCount = -1; while (oldCount != newCount) { oldCount = newCount; newCount = this.add(0, items).count(null, false); } return this; } /** * Remove an array of items from this inventory, * and return the result * * @param items The array of items * @return The resulting dInventory */ public dInventory remove(ItemStack[] items) { if (inventory == null || items == null) { return this; } for (ItemStack item : items) { if (item != null) { inventory.removeItem(item); } } return this; } /** * Remove a book from this inventory, comparing * only its title and author with books in the * inventory, but ignoring its text, thus having * Denizen support for updatable quest journals * and their like * * @param title The title of the book * @param author The author of the book * @param quantity The number of books to remove * @return The resulting dInventory */ public dInventory removeBook(String title, String author, int quantity) { if (inventory == null || (title == null && author == null)) { return this; } for (ItemStack invStack : inventory) { if (quantity == 0) { break; } if (invStack != null && invStack.getItemMeta() instanceof BookMeta) { BookMeta invMeta = (BookMeta) invStack.getItemMeta(); String invTitle = invMeta.getTitle(); String invAuthor = invMeta.getAuthor(); if ((invTitle == null && title != null) || (invAuthor == null && author != null)) { continue; } else if (invTitle == null || invAuthor == null) { continue; } if (equalOrNull(invAuthor, author) && equalOrNull(invTitle, title)) { // Make sure we don't remove more books than we need to // TODO: WTF is this logic??? if (quantity - invStack.getAmount() < 0) { invStack.setAmount((quantity - invStack.getAmount()) * -1); } else { inventory.removeItem(invStack); // Update the quantity we still have to remove quantity -= invStack.getAmount(); } } } } return this; } private static boolean equalOrNull(String a, String b) { return b == null || a == null || a.equalsIgnoreCase(b); } /** * Replace another inventory with this one, * cropping it if necessary so that it fits. * * @param destination The destination inventory */ public void replace(dInventory destination) { if (inventory == null || destination == null) { return; } // If the destination is smaller than our current inventory, // add as many items as possible if (destination.getSize() < this.getSize()) { destination.clear(); destination.add(0, this.getContents()); } else { destination.setContents(this.getContents()); } } public dInventory setSlots(int slot, ItemStack... items) { return setSlots(slot, items, items.length); } /** * Set items in an inventory, starting with a specified slot * * @param slot The slot to start from * @param items The items to add * @return The resulting dInventory */ public dInventory setSlots(int slot, ItemStack[] items, int c) { if (inventory == null || items == null) { return this; } for (int i = 0; i < c; i++) { if (i >= items.length || items[i] == null) { inventory.setItem(slot + i, new ItemStack(Material.AIR)); } ItemStack item = items[i]; if (slot + i < 0 || slot + i >= inventory.getSize()) { break; } inventory.setItem(slot + i, item); } if (Depends.citizens != null && dNPC.matches(idHolder)) { // TODO: Directly store holder dNPC.valueOf(idHolder).getInventoryTrait().setContents(inventory.getContents()); } return this; } public void clear() { if (inventory != null) { inventory.clear(); } } //////////////////////// // dObject Methods ///////////////////// private String prefix = getObjectType(); @Override public String getObjectType() { return "Inventory"; } @Override public String getPrefix() { return prefix; } @Override public dInventory setPrefix(String prefix) { this.prefix = prefix; return this; } @Override public String debug() { return "<G>" + prefix + "='<Y>" + identify() + "<G>' "; } @Override public String identify() { if (isUnique()) { return "in@" + NotableManager.getSavedId(this); } else { return "in@" + (getIdType().equals("script") ? idHolder : (idType + PropertyParser.getPropertiesString(this))); } } @Override public String identifySimple() { if (isUnique()) { return "in@" + NotableManager.getSavedId(this); } else { return "in@" + (getIdType().equals("script") || getIdType().equals("notable") ? idHolder : (idType + "[" + idHolder + ']')); } } @Override public String toString() { return identify(); } //////////////////////// // Attributes ///////////////////// public String getAttribute(Attribute attribute) { if (attribute == null) { return null; } // <--[tag] // @attribute <in@inventory.empty_slots> // @returns Element(Integer) // @description // Returns the number of empty slots in an inventory. // --> if (attribute.startsWith("empty_slots")) { dInventory dummyInv; if (inventory.getType() == InventoryType.PLAYER) { dummyInv = new dInventory(Bukkit.createInventory(null, InventoryType.CHEST)); ItemStack[] contents = getStorageContents(); dummyInv.setSize(contents.length); if (contents.length != dummyInv.getSize()) { contents = Arrays.copyOf(contents, dummyInv.getSize()); } dummyInv.setContents(contents); } else { dummyInv = new dInventory(inventory); } int full = dummyInv.count(null, true); return new Element(dummyInv.getSize() - full).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.can_fit[<item>]> // @returns Element(Boolean) // @description // Returns whether the inventory can fit an item. // --> if (attribute.startsWith("can_fit") && attribute.hasContext(1) && dItem.matches(attribute.getContext(1))) { dItem item = dItem.valueOf(attribute.getContext(1)); if (item == null) { return null; } int attribs = 1; int qty = 1; InventoryType type = inventory.getType(); dInventory dummyInv = new dInventory(Bukkit.createInventory(null, type == InventoryType.PLAYER ? InventoryType.CHEST : type, inventory.getTitle())); ItemStack[] contents = getStorageContents(); if (dummyInv.getInventoryType() == InventoryType.CHEST) { dummyInv.setSize(contents.length); } if (contents.length != dummyInv.getSize()) { contents = Arrays.copyOf(contents, dummyInv.getSize()); } dummyInv.setContents(contents); // <--[tag] // @attribute <in@inventory.can_fit[<item>].quantity[<#>]> // @returns Element(Boolean) // @description // Returns whether the inventory can fit a certain quantity of an item. // --> if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && attribute.hasContext(2) && aH.matchesInteger(attribute.getContext(2))) { qty = attribute.getIntContext(2); attribs = 2; } item.setAmount(qty); List<ItemStack> leftovers = dummyInv.addWithLeftovers(0, true, item.getItemStack()); return new Element(leftovers.isEmpty()).getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.include[<item>]> // @returns dInventory // @description // Returns the dInventory with an item added. // --> if (attribute.startsWith("include") && attribute.hasContext(1) && dItem.matches(attribute.getContext(1))) { dItem item = dItem.valueOf(attribute.getContext(1)); if (item == null) { return null; } int attribs = 1; int qty = 1; dInventory dummyInv = new dInventory(Bukkit.createInventory(null, inventory.getType(), inventory.getTitle())); if (inventory.getType() == InventoryType.CHEST) { dummyInv.setSize(inventory.getSize()); } dummyInv.setContents(getContents()); // <--[tag] // @attribute <in@inventory.include[<item>].quantity[<#>]> // @returns dInventory // @description // Returns the dInventory with a certain quantity of an item added. // --> if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && attribute.hasContext(2) && aH.matchesInteger(attribute.getContext(2))) { qty = attribute.getIntContext(2); attribs = 2; } item.setAmount(qty); dummyInv.add(0, item.getItemStack()); return dummyInv.getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.is_empty> // @returns Element(Boolean) // @description // Returns whether the inventory is empty. // --> if (attribute.startsWith("is_empty")) { boolean empty = true; for (ItemStack item : getStorageContents()) { if (item != null && item.getType() != Material.AIR) { empty = false; break; } } return new Element(empty).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.is_full> // @returns Element(Boolean) // @description // Returns whether the inventory is completely full. // --> if (attribute.startsWith("is_full")) { boolean full = true; for (ItemStack item : getStorageContents()) { if ((item == null) || (item.getType() == Material.AIR) || (item.getAmount() < item.getMaxStackSize())) { full = false; break; } } return new Element(full).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.contains.display[(strict:)<element>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains an item with the specified display // name. Use 'strict:' in front of the search element to ensure the display // name is EXACTLY the search element, otherwise the searching will only // check if the search element is contained in the display name. // --> if (attribute.startsWith("contains.display") && attribute.hasContext(2)) { String search_string = attribute.getContext(2); boolean strict = false; if (CoreUtilities.toLowerCase(search_string).startsWith("strict:") && search_string.length() > 7) { strict = true; search_string = search_string.substring(7); } if (search_string.length() == 0) { return null; } int qty = 1; int attribs = 2; // <--[tag] // @attribute <in@inventory.contains.display[(strict:)<element>].quantity[<#>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains a certain quantity of an item with the // specified display name. Use 'strict:' in front of the search element to ensure // the display name is EXACTLY the search element, otherwise the searching will only // check if the search element is contained in the display name. // --> if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && attribute.hasContext(3) && aH.matchesInteger(attribute.getContext(3))) { qty = attribute.getIntContext(3); attribs = 3; } int found_items = 0; if (strict) { for (ItemStack item : getContents()) { if (item != null && item.getType() == Material.WRITTEN_BOOK && ((BookMeta) item.getItemMeta()).getTitle().equalsIgnoreCase(search_string)) { found_items += item.getAmount(); if (found_items >= qty) { break; } } else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName() && item.getItemMeta().getDisplayName().equalsIgnoreCase(search_string)) { found_items += item.getAmount(); if (found_items >= qty) { break; } } } } else { for (ItemStack item : getContents()) { if (item != null && item.getType() == Material.WRITTEN_BOOK && CoreUtilities.toLowerCase(((BookMeta) item.getItemMeta()).getTitle()) .contains(CoreUtilities.toLowerCase(search_string))) { found_items += item.getAmount(); if (found_items >= qty) { break; } } else if (item != null && item.hasItemMeta() && item.getItemMeta().hasDisplayName() && CoreUtilities.toLowerCase(item.getItemMeta().getDisplayName()) .contains(CoreUtilities.toLowerCase(search_string))) { found_items += item.getAmount(); if (found_items >= qty) { break; } } } } return new Element(found_items >= qty).getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.contains.lore[(strict:)<element>|...]> // @returns Element(Boolean) // @description // Returns whether the inventory contains an item with the specified lore. // Use 'strict:' in front of the search elements to ensure all lore lines // are EXACTLY the search elements, otherwise the searching will only // check if the search elements are contained in the lore. // --> if (attribute.startsWith("contains.lore") && attribute.hasContext(2)) { String search_string = attribute.getContext(2); boolean strict = false; if (CoreUtilities.toLowerCase(search_string).startsWith("strict:")) { strict = true; search_string = search_string.substring(7); } if (search_string.length() == 0) { return null; } dList lore = dList.valueOf(search_string); int qty = 1; int attribs = 2; // <--[tag] // @attribute <in@inventory.contains.lore[(strict:)<element>|...].quantity[<#>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains a certain quantity of an item // with the specified lore. Use 'strict:' in front of the search elements // to ensure all lore lines are EXACTLY the search elements, otherwise the // searching will only check if the search elements are contained in the lore. // --> if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && attribute.hasContext(3) && aH.matchesInteger(attribute.getContext(3))) { qty = attribute.getIntContext(3); attribs = 3; } int found_items = 0; if (strict) { strict_items: for (ItemStack item : getContents()) { if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { List<String> item_lore = item.getItemMeta().getLore(); if (lore.size() != item_lore.size()) { continue; } for (int i = 0; i < item_lore.size(); i++) { if (lore.get(i).equalsIgnoreCase(item_lore.get(i))) { if (i == lore.size()) { found_items += item.getAmount(); if (found_items >= qty) { break strict_items; } } } else { continue strict_items; } } } } } else { for (ItemStack item : getContents()) { if (item != null && item.hasItemMeta() && item.getItemMeta().hasLore()) { List<String> item_lore = item.getItemMeta().getLore(); int loreCount = 0; lines: for (String line : lore) { for (String item_line : item_lore) { if (CoreUtilities.toLowerCase(item_line).contains(CoreUtilities.toLowerCase(line))) { loreCount++; continue lines; } } } if (loreCount == lore.size()) { found_items += item.getAmount(); if (found_items >= qty) { break; } } } } } return new Element(found_items >= qty).getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.contains.material[<material>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains an item with the specified material. // --> if (attribute.startsWith("contains.material") && attribute.hasContext(2) && dMaterial.matches(attribute.getContext(2))) { dMaterial material = dMaterial.valueOf(attribute.getContext(2)); int qty = 1; int attribs = 2; // <--[tag] // @attribute <in@inventory.contains.material[<material>].quantity[<#>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains a certain quantity of an item with the // specified material. // --> if ((attribute.getAttribute(3).startsWith("quantity") || attribute.getAttribute(3).startsWith("qty")) && attribute.hasContext(3) && aH.matchesInteger(attribute.getContext(3))) { qty = attribute.getIntContext(3); attribs = 3; } int found_items = 0; for (ItemStack item : getContents()) { if (item != null && item.getType() == material.getMaterial()) { found_items += item.getAmount(); if (found_items >= qty) { break; } } } return new Element(found_items >= qty).getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.contains_any[<item>|...]> // @returns Element(Boolean) // @description // Returns whether the inventory contains any of the specified items. // --> if (attribute.startsWith("contains_any") && attribute.hasContext(1)) { dList list = dList.valueOf(attribute.getContext(1)); if (list.isEmpty()) { return null; } int qty = 1; int attribs = 1; // <--[tag] // @attribute <in@inventory.contains_any[<item>|...].quantity[<#>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains a certain quantity of any of the specified items. // --> if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && attribute.hasContext(2) && aH.matchesInteger(attribute.getContext(2))) { qty = attribute.getIntContext(2); attribs = 2; } List<dItem> contains = list.filter(dItem.class, attribute.getScriptEntry()); if (!contains.isEmpty()) { for (dItem item : contains) { if (containsItem(item, qty)) { return Element.TRUE.getAttribute(attribute.fulfill(attribs)); } } } return Element.FALSE.getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.contains[<item>|...]> // @returns Element(Boolean) // @description // Returns whether the inventory contains all of the specified items. // --> if (attribute.startsWith("contains") && attribute.hasContext(1)) { dList list = dList.valueOf(attribute.getContext(1)); if (list.isEmpty()) { return null; } int qty = 1; int attribs = 1; // <--[tag] // @attribute <in@inventory.contains[<item>|...].quantity[<#>]> // @returns Element(Boolean) // @description // Returns whether the inventory contains a certain quantity of all of the specified items. // --> if ((attribute.getAttribute(2).startsWith("quantity") || attribute.getAttribute(2).startsWith("qty")) && attribute.hasContext(2) && aH.matchesInteger(attribute.getContext(2))) { qty = attribute.getIntContext(2); attribs = 2; } // TODO: Fix logic List<dItem> contains = list.filter(dItem.class, attribute.getScriptEntry()); if (!contains.isEmpty()) { for (dItem item : contains) { if (containsItem(item, qty)) { return Element.TRUE.getAttribute(attribute.fulfill(attribs)); } } } return Element.FALSE.getAttribute(attribute.fulfill(attribs)); } // <--[tag] // @attribute <in@inventory.first_empty> // @returns Element(Number) // @description // Returns the location of the first empty slot. // Returns -1 if the inventory is full. // --> if (attribute.startsWith("first_empty")) { return new Element(firstEmpty(0)).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.find.material[<material>]> // @returns Element(Number) // @description // Returns the location of the first slot that contains the material. // Returns -1 if there's no match. // --> if (attribute.startsWith("find.material") && attribute.hasContext(2) && dMaterial.matches(attribute.getContext(2))) { dMaterial material = dMaterial.valueOf(attribute.getContext(2)); if (material == null) { return null; } int slot = -1; for (int i = 0; i < inventory.getSize(); i++) { if (inventory.getItem(i) != null && inventory.getItem(i).getType() == material.getMaterial()) { slot = i + 1; break; } } return new Element(slot).getAttribute(attribute.fulfill(2)); } // <--[tag] // @attribute <in@inventory.find_imperfect[<item>]> // @returns Element(Number) // @description // Returns the location of the first slot that contains the item. // Returns -1 if there's no match. // Will match item script to item script, even if one is edited. // --> if (attribute.startsWith("find_imperfect") && attribute.hasContext(1) && dItem.matches(attribute.getContext(1))) { dItem item = dItem.valueOf(attribute.getContext(1), attribute.getScriptEntry() != null ? ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer() : null, attribute.getScriptEntry() != null ? ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getNPC() : null); item.setAmount(1); int slot = -1; for (int i = 0; i < inventory.getSize(); i++) { if (inventory.getItem(i) != null) { dItem compare_to = new dItem(inventory.getItem(i).clone()); compare_to.setAmount(1); if (item.identify().equalsIgnoreCase(compare_to.identify())) { slot = i + 1; break; } } } return new Element(slot).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.find[<item>]> // @returns Element(Number) // @description // Returns the location of the first slot that contains the item. // Returns -1 if there's no match. // --> if (attribute.startsWith("find") && attribute.hasContext(1) && dItem.matches(attribute.getContext(1))) { dItem item = dItem.valueOf(attribute.getContext(1), attribute.getScriptEntry() != null ? ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer() : null, attribute.getScriptEntry() != null ? ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getNPC() : null); item.setAmount(1); int slot = -1; for (int i = 0; i < inventory.getSize(); i++) { if (inventory.getItem(i) != null) { dItem compare_to = new dItem(inventory.getItem(i).clone()); compare_to.setAmount(1); if (item.getFullString().equalsIgnoreCase(compare_to.getFullString())) { slot = i + 1; break; } } } return new Element(slot).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.id_type> // @returns Element // @description // Returns Denizen's type ID for this inventory. (player, location, etc.) // --> if (attribute.startsWith("id_type")) { return new Element(idType).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.notable_name> // @returns Element // @description // Gets the name of a Notable dInventory. If the inventory isn't noted, // this is null. // --> if (attribute.startsWith("notable_name")) { String notname = NotableManager.getSavedId(this); if (notname == null) { return null; } return new Element(notname).getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.location> // @returns dLocation // @description // Returns the location of this inventory's holder. // --> if (attribute.startsWith("location")) { dLocation location = getLocation(); if (location == null) { return null; } return location.getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.quantity[<item>]> // @returns Element(Number) // @description // Returns the combined quantity of itemstacks that match an item if // one is specified, or the combined quantity of all itemstacks // if one is not. // --> if (attribute.startsWith("quantity") || attribute.startsWith("qty")) { if (attribute.hasContext(1) && dItem.matches(attribute.getContext(1))) { return new Element(count // TODO: Handle no-script-entry cases (dItem.valueOf(attribute.getContext(1), ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer(), ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getNPC()).getItemStack(), false)) .getAttribute(attribute.fulfill(1)); } else { return new Element(count(null, false)) .getAttribute(attribute.fulfill(1)); } } // <--[tag] // @attribute <in@inventory.stacks[<item>]> // @returns Element(Number) // @description // Returns the number of itemstacks that match an item if one is // specified, or the number of all itemstacks if one is not. // --> if (attribute.startsWith("stacks")) { if (attribute.hasContext(1) && dItem.matches(attribute.getContext(1))) { return new Element(count // TODO: Handle no-script-entry cases (dItem.valueOf(attribute.getContext(1), ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getPlayer(), ((BukkitScriptEntryData) attribute.getScriptEntry().entryData).getNPC()).getItemStack(), true)) .getAttribute(attribute.fulfill(1)); } else { return new Element(count(null, true)) .getAttribute(attribute.fulfill(1)); } } // <--[tag] // @attribute <in@inventory.slot[<#>]> // @returns dItem // @description // Returns the item in the specified slot. // --> if (attribute.startsWith("slot") && attribute.hasContext(1) && aH.matchesInteger(attribute.getContext(1))) { int slot = new Element(attribute.getContext(1)).asInt() - 1; if (slot < 0) { slot = 0; } else if (slot > getInventory().getSize() - 1) { slot = getInventory().getSize() - 1; } return new dItem(getInventory().getItem(slot)) .getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.inventory_type> // @returns Element // @description // Returns the type of the inventory (e.g. "PLAYER", "CRAFTING", "HORSE"). // --> if (attribute.startsWith("inventory_type")) { return new Element(inventory instanceof HorseInventory ? "HORSE" : getInventory().getType().name()) .getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.equipment> // @returns dList(dItem) // @description // Returns the equipment of an inventory. // --> if (attribute.startsWith("equipment")) { dList equipment = getEquipment(); if (equipment == null) { return null; } return equipment.getAttribute(attribute.fulfill(1)); } if (inventory instanceof CraftingInventory) { CraftingInventory craftingInventory = (CraftingInventory) inventory; // <--[tag] // @attribute <in@inventory.matrix> // @returns dList(dItem) // @description // Returns the dItems currently in a crafting inventory's matrix. // --> if (attribute.startsWith("matrix")) { dList recipeList = new dList(); for (ItemStack item : craftingInventory.getMatrix()) { if (item != null) { recipeList.add(new dItem(item).identify()); } else { recipeList.add(new dItem(Material.AIR).identify()); } } return recipeList.getAttribute(attribute.fulfill(1)); } // <--[tag] // @attribute <in@inventory.result> // @returns dItem // @description // Returns the dItem currently in the result section of a crafting inventory. // --> if (attribute.startsWith("result")) { ItemStack result = craftingInventory.getResult(); if (result == null) { return null; } return new dItem(result).getAttribute(attribute.fulfill(1)); } } // <--[tag] // @attribute <in@inventory.type> // @returns Element // @description // Always returns 'Inventory' for dInventory objects. All objects fetchable by the Object Fetcher will return the // type of object that is fulfilling this attribute. // --> if (attribute.startsWith("type")) { return new Element("Inventory").getAttribute(attribute.fulfill(1)); } // Iterate through this object's properties' attributes for (Property property : PropertyParser.getProperties(this)) { String returned = property.getAttribute(attribute); if (returned != null) { return returned; } } return new Element(identify()).getAttribute(attribute); } private ArrayList<Mechanism> mechanisms = new ArrayList<Mechanism>(); public void applyProperty(Mechanism mechanism) { if (idType == null) { mechanisms.add(mechanism); } else if (idType.equals("generic") || mechanism.matches("holder")) { adjust(mechanism); } else if (!(idType.equals("location") && mechanism.matches("title"))) { dB.echoError("Cannot apply properties to non-generic inventory!"); } } @Override public void adjust(Mechanism mechanism) { // Iterate through this object's properties' mechanisms for (Property property : PropertyParser.getProperties(this)) { property.adjust(mechanism); if (mechanism.fulfilled()) { break; } } if (inventory instanceof CraftingInventory) { CraftingInventory craftingInventory = (CraftingInventory) inventory; // <--[mechanism] // @object dInventory // @name matrix // @input dList(dItem) // @description // Sets the items in the matrix slots of this crafting inventory. // @tags // <in@inventory.matrix> // --> if (mechanism.matches("matrix") && mechanism.requireObject(dList.class)) { List<dItem> items = mechanism.getValue().asType(dList.class).filter(dItem.class); ItemStack[] itemStacks = new ItemStack[9]; for (int i = 0; i < 9 && i < items.size(); i++) { itemStacks[i] = items.get(i).getItemStack(); } craftingInventory.setMatrix(itemStacks); ((Player) inventory.getHolder()).updateInventory(); } // <--[mechanism] // @object dInventory // @name result // @input dItem // @description // Sets the item in the result slot of this crafting inventory. // @tags // <in@inventory.result> // --> if (mechanism.matches("result") && mechanism.requireObject(dItem.class)) { craftingInventory.setResult(mechanism.getValue().asType(dItem.class).getItemStack()); ((Player) inventory.getHolder()).updateInventory(); } } if (!mechanism.fulfilled()) { mechanism.reportInvalid(); } } }