From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: William Blake Galbreath <Blake.Galbreath@GMail.com>
Date: Sun, 15 Mar 2020 20:52:12 -0500
Subject: [PATCH] ItemStack convenience methods


diff --git a/src/main/java/org/bukkit/Material.java b/src/main/java/org/bukkit/Material.java
index 9b1c9e60dba9ea3ef8d8e164f13dd76daf57db8e..5b41f1016ed2d5ace2b47d3dd398314230af84b7 100644
--- a/src/main/java/org/bukkit/Material.java
+++ b/src/main/java/org/bukkit/Material.java
@@ -8718,4 +8718,36 @@ public enum Material implements Keyed {
             // </editor-fold>
         }
     }
+
+    // Purpur start
+    public boolean isArmor() {
+        switch (this) {
+            // <editor-fold defaultstate="collapsed" desc="isarmor">
+            case LEATHER_BOOTS:
+            case LEATHER_CHESTPLATE:
+            case LEATHER_HELMET:
+            case LEATHER_LEGGINGS:
+            case CHAINMAIL_BOOTS:
+            case CHAINMAIL_CHESTPLATE:
+            case CHAINMAIL_HELMET:
+            case CHAINMAIL_LEGGINGS:
+            case IRON_BOOTS:
+            case IRON_CHESTPLATE:
+            case IRON_HELMET:
+            case IRON_LEGGINGS:
+            case GOLDEN_BOOTS:
+            case GOLDEN_CHESTPLATE:
+            case GOLDEN_HELMET:
+            case GOLDEN_LEGGINGS:
+            case DIAMOND_BOOTS:
+            case DIAMOND_CHESTPLATE:
+            case DIAMOND_HELMET:
+            case DIAMOND_LEGGINGS:
+            case TURTLE_HELMET:
+                return true;
+            default:
+                return false;
+        }
+    }
+    // Purpur end
 }
diff --git a/src/main/java/org/bukkit/inventory/ItemStack.java b/src/main/java/org/bukkit/inventory/ItemStack.java
index 58f99e3ebac9a01ebffe4d208e16cbee474d4aa3..314658c7dac1b1fbb440ec85133e6670e52ba684 100644
--- a/src/main/java/org/bukkit/inventory/ItemStack.java
+++ b/src/main/java/org/bukkit/inventory/ItemStack.java
@@ -17,6 +17,18 @@ import org.bukkit.inventory.meta.ItemMeta;
 import org.bukkit.material.MaterialData;
 import org.jetbrains.annotations.NotNull;
 import org.jetbrains.annotations.Nullable;
+// Purpur start
+import com.google.common.collect.Multimap;
+import java.util.Collection;
+import org.bukkit.attribute.Attribute;
+import org.bukkit.attribute.AttributeModifier;
+import org.bukkit.block.data.BlockData;
+import org.bukkit.inventory.meta.BlockDataMeta;
+import org.bukkit.inventory.meta.Repairable;
+import org.bukkit.persistence.PersistentDataContainer;
+import org.bukkit.persistence.PersistentDataHolder;
+import com.destroystokyo.paper.Namespaced;
+// Purpur end
 
 /**
  * Represents a stack of items.
@@ -843,4 +855,627 @@ public class ItemStack implements Cloneable, ConfigurationSerializable, net.kyor
         return itemMeta != null && itemMeta.hasItemFlag(flag);
     }
     // Paper end
+
+    // Purpur start
+    /**
+     * Gets the display name that is set.
+     * <p>
+     * Plugins should check that hasDisplayName() returns <code>true</code>
+     * before calling this method.
+     *
+     * @return the display name that is set
+     */
+    @NotNull
+    public String getDisplayName() {
+        return getItemMeta().getDisplayName();
+    }
+
+    /**
+     * Sets the display name.
+     *
+     * @param name the name to set
+     */
+    public void setDisplayName(@Nullable String name) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setDisplayName(name);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Checks for existence of a display name.
+     *
+     * @return true if this has a display name
+     */
+    public boolean hasDisplayName() {
+        return getItemMeta().hasDisplayName();
+    }
+
+    /**
+     * Gets the localized display name that is set.
+     * <p>
+     * Plugins should check that hasLocalizedName() returns <code>true</code>
+     * before calling this method.
+     *
+     * @return the localized name that is set
+     */
+    @NotNull
+    public String getLocalizedName() {
+        return getItemMeta().getLocalizedName();
+    }
+
+    /**
+     * Sets the localized name.
+     *
+     * @param name the name to set
+     */
+    public void setLocalizedName(@Nullable String name) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setLocalizedName(name);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Checks for existence of a localized name.
+     *
+     * @return true if this has a localized name
+     */
+    public boolean hasLocalizedName() {
+        return getItemMeta().hasLocalizedName();
+    }
+
+    /**
+     * Checks for existence of lore.
+     *
+     * @return true if this has lore
+     */
+    public boolean hasLore() {
+        return getItemMeta().hasLore();
+    }
+
+    /**
+     * Checks for existence of the specified enchantment.
+     *
+     * @param ench enchantment to check
+     * @return true if this enchantment exists for this meta
+     */
+    public boolean hasEnchant(@NotNull Enchantment ench) {
+        return getItemMeta().hasEnchant(ench);
+    }
+
+    /**
+     * Checks for the level of the specified enchantment.
+     *
+     * @param ench enchantment to check
+     * @return The level that the specified enchantment has, or 0 if none
+     */
+    public int getEnchantLevel(@NotNull Enchantment ench) {
+        return getItemMeta().getEnchantLevel(ench);
+    }
+
+    /**
+     * Returns a copy the enchantments in this ItemMeta. <br>
+     * Returns an empty map if none.
+     *
+     * @return An immutable copy of the enchantments
+     */
+    @NotNull
+    public Map<Enchantment, Integer> getEnchants() {
+        return getItemMeta().getEnchants();
+    }
+
+    /**
+     * Adds the specified enchantment to this item meta.
+     *
+     * @param ench Enchantment to add
+     * @param level Level for the enchantment
+     * @param ignoreLevelRestriction this indicates the enchantment should be
+     *     applied, ignoring the level limit
+     * @return true if the item meta changed as a result of this call, false
+     *     otherwise
+     */
+    public boolean addEnchant(@NotNull Enchantment ench, int level, boolean ignoreLevelRestriction) {
+        ItemMeta itemMeta = getItemMeta();
+        boolean result = itemMeta.addEnchant(ench, level, ignoreLevelRestriction);
+        setItemMeta(itemMeta);
+        return result;
+    }
+
+    /**
+     * Removes the specified enchantment from this item meta.
+     *
+     * @param ench Enchantment to remove
+     * @return true if the item meta changed as a result of this call, false
+     *     otherwise
+     */
+    public boolean removeEnchant(@NotNull Enchantment ench) {
+        ItemMeta itemMeta = getItemMeta();
+        boolean result = itemMeta.removeEnchant(ench);
+        setItemMeta(itemMeta);
+        return result;
+    }
+
+    /**
+     * Checks for the existence of any enchantments.
+     *
+     * @return true if an enchantment exists on this meta
+     */
+    public boolean hasEnchants() {
+        return getItemMeta().hasEnchants();
+    }
+
+    /**
+     * Checks if the specified enchantment conflicts with any enchantments in
+     * this ItemMeta.
+     *
+     * @param ench enchantment to test
+     * @return true if the enchantment conflicts, false otherwise
+     */
+    public boolean hasConflictingEnchant(@NotNull Enchantment ench) {
+        return getItemMeta().hasConflictingEnchant(ench);
+    }
+
+    /**
+     * Sets the custom model data.
+     * <p>
+     * CustomModelData is an integer that may be associated client side with a
+     * custom item model.
+     *
+     * @param data the data to set, or null to clear
+     */
+    public void setCustomModelData(@Nullable Integer data) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setCustomModelData(data);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Gets the custom model data that is set.
+     * <p>
+     * CustomModelData is an integer that may be associated client side with a
+     * custom item model.
+     * <p>
+     * Plugins should check that hasCustomModelData() returns <code>true</code>
+     * before calling this method.
+     *
+     * @return the localized name that is set
+     */
+    public int getCustomModelData() {
+        return getItemMeta().getCustomModelData();
+    }
+
+    /**
+     * Checks for existence of custom model data.
+     * <p>
+     * CustomModelData is an integer that may be associated client side with a
+     * custom item model.
+     *
+     * @return true if this has custom model data
+     */
+    public boolean hasCustomModelData() {
+        return getItemMeta().hasCustomModelData();
+    }
+
+    /**
+     * Returns whether the item has block data currently attached to it.
+     *
+     * @return whether block data is already attached
+     */
+    public boolean hasBlockData() {
+        return ((BlockDataMeta) getItemMeta()).hasBlockData();
+    }
+
+    /**
+     * Returns the currently attached block data for this item or creates a new
+     * one if one doesn't exist.
+     *
+     * The state is a copy, it must be set back (or to another item) with
+     * {@link #setBlockData(BlockData)}
+     *
+     * @param material the material we wish to get this data in the context of
+     * @return the attached data or new data
+     */
+    @NotNull
+    public BlockData getBlockData(@NotNull Material material) {
+        return ((BlockDataMeta) getItemMeta()).getBlockData(material);
+    }
+
+    /**
+     * Attaches a copy of the passed block data to the item.
+     *
+     * @param blockData the block data to attach to the block.
+     * @throws IllegalArgumentException if the blockData is null or invalid for
+     * this item.
+     */
+    public void setBlockData(@NotNull BlockData blockData) {
+        ItemMeta itemMeta = getItemMeta();
+        ((BlockDataMeta) itemMeta).setBlockData(blockData);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Gets the repair penalty
+     *
+     * @return the repair penalty
+     */
+    public int getRepairCost() {
+        return ((Repairable) getItemMeta()).getRepairCost();
+    }
+
+    /**
+     * Sets the repair penalty
+     *
+     * @param cost repair penalty
+     */
+    public void setRepairCost(int cost) {
+        ItemMeta itemMeta = getItemMeta();
+        ((Repairable) itemMeta).setRepairCost(cost);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Checks to see if this has a repair penalty
+     *
+     * @return true if this has a repair penalty
+     */
+    public boolean hasRepairCost() {
+        return ((Repairable) getItemMeta()).hasRepairCost();
+    }
+
+    /**
+     * Return if the unbreakable tag is true. An unbreakable item will not lose
+     * durability.
+     *
+     * @return true if the unbreakable tag is true
+     */
+    public boolean isUnbreakable() {
+        return getItemMeta().isUnbreakable();
+    }
+
+    /**
+     * Sets the unbreakable tag. An unbreakable item will not lose durability.
+     *
+     * @param unbreakable true if set unbreakable
+     */
+    public void setUnbreakable(boolean unbreakable) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setUnbreakable(unbreakable);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Checks for the existence of any AttributeModifiers.
+     *
+     * @return true if any AttributeModifiers exist
+     */
+    public boolean hasAttributeModifiers() {
+        return getItemMeta().hasAttributeModifiers();
+    }
+
+    /**
+     * Return an immutable copy of all Attributes and
+     * their modifiers in this ItemMeta.<br>
+     * Returns null if none exist.
+     *
+     * @return an immutable {@link Multimap} of Attributes
+     *         and their AttributeModifiers, or null if none exist
+     */
+    @Nullable
+    public Multimap<Attribute, AttributeModifier> getAttributeModifiers() {
+        return getItemMeta().getAttributeModifiers();
+    }
+
+    /**
+     * Return an immutable copy of all {@link Attribute}s and their
+     * {@link AttributeModifier}s for a given {@link EquipmentSlot}.<br>
+     * Any {@link AttributeModifier} that does have have a given
+     * {@link EquipmentSlot} will be returned. This is because
+     * AttributeModifiers without a slot are active in any slot.<br>
+     * If there are no attributes set for the given slot, an empty map
+     * will be returned.
+     *
+     * @param slot the {@link EquipmentSlot} to check
+     * @return the immutable {@link Multimap} with the
+     *         respective Attributes and modifiers, or an empty map
+     *         if no attributes are set.
+     */
+    @NotNull
+    public Multimap<Attribute, AttributeModifier> getAttributeModifiers(@Nullable EquipmentSlot slot) {
+        return getItemMeta().getAttributeModifiers(slot);
+    }
+
+    /**
+     * Return an immutable copy of all {@link AttributeModifier}s
+     * for a given {@link Attribute}
+     *
+     * @param attribute the {@link Attribute}
+     * @return an immutable collection of {@link AttributeModifier}s
+     *          or null if no AttributeModifiers exist for the Attribute.
+     * @throws NullPointerException if Attribute is null
+     */
+    @Nullable
+    public Collection<AttributeModifier> getAttributeModifiers(@NotNull Attribute attribute) {
+        return getItemMeta().getAttributeModifiers(attribute);
+    }
+
+    /**
+     * Add an Attribute and it's Modifier.
+     * AttributeModifiers can now support {@link EquipmentSlot}s.
+     * If not set, the {@link AttributeModifier} will be active in ALL slots.
+     * <br>
+     * Two {@link AttributeModifier}s that have the same {@link java.util.UUID}
+     * cannot exist on the same Attribute.
+     *
+     * @param attribute the {@link Attribute} to modify
+     * @param modifier the {@link AttributeModifier} specifying the modification
+     * @return true if the Attribute and AttributeModifier were
+     *         successfully added
+     * @throws NullPointerException if Attribute is null
+     * @throws NullPointerException if AttributeModifier is null
+     * @throws IllegalArgumentException if AttributeModifier already exists
+     */
+    public boolean addAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) {
+        ItemMeta itemMeta = getItemMeta();
+        boolean result = itemMeta.addAttributeModifier(attribute, modifier);
+        setItemMeta(itemMeta);
+        return result;
+    }
+
+    /**
+     * Set all {@link Attribute}s and their {@link AttributeModifier}s.
+     * To clear all currently set Attributes and AttributeModifiers use
+     * null or an empty Multimap.
+     * If not null nor empty, this will filter all entries that are not-null
+     * and add them to the ItemStack.
+     *
+     * @param attributeModifiers the new Multimap containing the Attributes
+     *                           and their AttributeModifiers
+     */
+    public void setAttributeModifiers(@Nullable Multimap<Attribute, AttributeModifier> attributeModifiers) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setAttributeModifiers(attributeModifiers);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Remove all {@link AttributeModifier}s associated with the given
+     * {@link Attribute}.
+     * This will return false if nothing was removed.
+     *
+     * @param attribute attribute to remove
+     * @return  true if all modifiers were removed from a given
+     *                  Attribute. Returns false if no attributes were
+     *                  removed.
+     * @throws NullPointerException if Attribute is null
+     */
+    public boolean removeAttributeModifier(@NotNull Attribute attribute) {
+        ItemMeta itemMeta = getItemMeta();
+        boolean result = itemMeta.removeAttributeModifier(attribute);
+        setItemMeta(itemMeta);
+        return result;
+    }
+
+    /**
+     * Remove all {@link Attribute}s and {@link AttributeModifier}s for a
+     * given {@link EquipmentSlot}.<br>
+     * If the given {@link EquipmentSlot} is null, this will remove all
+     * {@link AttributeModifier}s that do not have an EquipmentSlot set.
+     *
+     * @param slot the {@link EquipmentSlot} to clear all Attributes and
+     *             their modifiers for
+     * @return true if all modifiers were removed that match the given
+     *         EquipmentSlot.
+     */
+    public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) {
+        ItemMeta itemMeta = getItemMeta();
+        boolean result = itemMeta.removeAttributeModifier(slot);
+        setItemMeta(itemMeta);
+        return result;
+    }
+
+    /**
+     * Remove a specific {@link Attribute} and {@link AttributeModifier}.
+     * AttributeModifiers are matched according to their {@link java.util.UUID}.
+     *
+     * @param attribute the {@link Attribute} to remove
+     * @param modifier the {@link AttributeModifier} to remove
+     * @return if any attribute modifiers were remove
+     *
+     * @throws NullPointerException if the Attribute is null
+     * @throws NullPointerException if the AttributeModifier is null
+     *
+     * @see AttributeModifier#getUniqueId()
+     */
+    public boolean removeAttributeModifier(@NotNull Attribute attribute, @NotNull AttributeModifier modifier) {
+        ItemMeta itemMeta = getItemMeta();
+        boolean result = itemMeta.removeAttributeModifier(attribute, modifier);
+        setItemMeta(itemMeta);
+        return result;
+    }
+
+    /**
+     * Returns a custom tag container capable of storing tags on the object.
+     *
+     * Note that the tags stored on this container are all stored under their
+     * own custom namespace therefore modifying default tags using this
+     * {@link PersistentDataHolder} is impossible.
+     *
+     * @return the persistent metadata container
+     */
+    @NotNull
+    public PersistentDataContainer getPersistentDataContainer() {
+        return getItemMeta().getPersistentDataContainer();
+    }
+
+    /**
+     * Checks to see if this item has damage
+     *
+     * @return true if this has damage
+     */
+    public boolean hasDamage() {
+        return ((Damageable) getItemMeta()).hasDamage();
+    }
+
+    /**
+     * Gets the damage
+     *
+     * @return the damage
+     */
+    public int getDamage() {
+        return ((Damageable) getItemMeta()).getDamage();
+    }
+
+    /**
+     * Sets the damage
+     *
+     * @param damage item damage
+     */
+    public void setDamage(int damage) {
+        ItemMeta itemMeta = getItemMeta();
+        ((Damageable) itemMeta).setDamage(damage);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Gets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE}
+     *
+     * @return Set of {@link com.destroystokyo.paper.Namespaced}
+     */
+    @NotNull
+    public Set<Namespaced> getDestroyableKeys() {
+        return getItemMeta().getDestroyableKeys();
+    }
+
+    /**
+     * Sets the collection of namespaced keys that the item can destroy in {@link org.bukkit.GameMode#ADVENTURE}
+     *
+     * @param canDestroy Collection of {@link com.destroystokyo.paper.Namespaced}
+     */
+    public void setDestroyableKeys(@NotNull Collection<Namespaced> canDestroy) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setDestroyableKeys(canDestroy);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Gets the collection of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE}
+     *
+     * @return Set of {@link com.destroystokyo.paper.Namespaced}
+     */
+    @NotNull
+    public Set<Namespaced> getPlaceableKeys() {
+        return getItemMeta().getPlaceableKeys();
+    }
+
+    /**
+     * Sets the set of namespaced keys that the item can be placed on in {@link org.bukkit.GameMode#ADVENTURE}
+     *
+     * @param canPlaceOn Collection of {@link com.destroystokyo.paper.Namespaced}
+     */
+    @NotNull
+    public void setPlaceableKeys(@NotNull Collection<Namespaced> canPlaceOn) {
+        ItemMeta itemMeta = getItemMeta();
+        itemMeta.setPlaceableKeys(canPlaceOn);
+        setItemMeta(itemMeta);
+    }
+
+    /**
+     * Checks for the existence of any keys that the item can be placed on
+     *
+     * @return true if this item has placeable keys
+     */
+    public boolean hasPlaceableKeys() {
+        return getItemMeta().hasPlaceableKeys();
+    }
+
+    /**
+     * Checks for the existence of any keys that the item can destroy
+     *
+     * @return true if this item has destroyable keys
+     */
+    public boolean hasDestroyableKeys() {
+        return getItemMeta().hasDestroyableKeys();
+    }
+
+    /**
+     * Repairs this item by 1 durability
+     */
+    public void repair() {
+        repair(1);
+    }
+
+    /**
+     * Damages this item by 1 durability
+     *
+     * @return True if damage broke the item
+     */
+    public boolean damage() {
+        return damage(1);
+    }
+
+    /**
+     * Repairs this item's durability by amount
+     *
+     * @param amount Amount of durability to repair
+     */
+    public void repair(int amount) {
+        damage(-amount);
+    }
+
+    /**
+     * Damages this item's durability by amount
+     *
+     * @param amount Amount of durability to damage
+     * @return True if damage broke the item
+     */
+    public boolean damage(int amount) {
+        return damage(amount, false);
+    }
+
+    /**
+     * Damages this item's durability by amount
+     *
+     * @param amount Amount of durability to damage
+     * @param ignoreUnbreaking Ignores unbreaking enchantment
+     * @return True if damage broke the item
+     */
+    public boolean damage(int amount, boolean ignoreUnbreaking) {
+        Damageable damageable = (Damageable) getItemMeta();
+        if (amount > 0) {
+            int unbreaking = getEnchantLevel(Enchantment.DURABILITY);
+            int reduce = 0;
+            for (int i = 0; unbreaking > 0 && i < amount; ++i) {
+                if (reduceDamage(java.util.concurrent.ThreadLocalRandom.current(), unbreaking)) {
+                    ++reduce;
+                }
+            }
+            amount -= reduce;
+            if (amount <= 0) {
+                return isBroke(damageable.getDamage());
+            }
+        }
+        int damage = damageable.getDamage() + amount;
+        damageable.setDamage(damage);
+        setItemMeta((ItemMeta) damageable);
+        return isBroke(damage);
+    }
+
+    public boolean isBroke(int damage) {
+        if (damage > getType().getMaxDurability()) {
+            if (getAmount() > 0) {
+                // ensure it "breaks"
+                setAmount(0);
+            }
+            return true;
+        }
+        return false;
+    }
+
+    private boolean reduceDamage(java.util.Random random, int unbreaking) {
+        if (getType().isArmor()) {
+            return random.nextFloat() < 0.6F;
+        }
+        return random.nextInt(unbreaking + 1) > 0;
+    }
+
+    // Purpur end
 }
