From 8164f4b25bb557d4ded2db42ca4e6abb423bb5ee Mon Sep 17 00:00:00 2001 From: Senmori Date: Fri, 21 Sep 2018 20:47:37 +1000 Subject: [PATCH] SPIGOT-1916: Attribute modifiers for ItemStacks --- .../attribute/CraftAttributeInstance.java | 4 +- .../attribute/CraftAttributeMap.java | 18 +- .../craftbukkit/inventory/CraftMetaItem.java | 350 +++++++++++++++--- 3 files changed, 317 insertions(+), 55 deletions(-) diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java index 31e608a6a2..07b457e8a3 100644 --- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java +++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeInstance.java @@ -65,11 +65,11 @@ public class CraftAttributeInstance implements AttributeInstance { return handle.getAttribute().getDefault(); } - private static net.minecraft.server.AttributeModifier convert(AttributeModifier bukkit) { + public static net.minecraft.server.AttributeModifier convert(AttributeModifier bukkit) { return new net.minecraft.server.AttributeModifier(bukkit.getUniqueId(), bukkit.getName(), bukkit.getAmount(), bukkit.getOperation().ordinal()); } - private static AttributeModifier convert(net.minecraft.server.AttributeModifier nms) { + public static AttributeModifier convert(net.minecraft.server.AttributeModifier nms) { return new AttributeModifier(nms.a(), nms.b(), nms.d(), AttributeModifier.Operation.values()[nms.c()]); } } diff --git a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java index e3adb5bda1..77e584b129 100644 --- a/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java +++ b/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttributeMap.java @@ -1,7 +1,10 @@ package org.bukkit.craftbukkit.attribute; +import com.google.common.base.CaseFormat; import com.google.common.base.Preconditions; +import java.util.Locale; import net.minecraft.server.AttributeMapBase; +import org.apache.commons.lang3.EnumUtils; import org.bukkit.attribute.Attributable; import org.bukkit.attribute.Attribute; import org.bukkit.attribute.AttributeInstance; @@ -22,7 +25,7 @@ public class CraftAttributeMap implements Attributable { return (nms == null) ? null : new CraftAttributeInstance(nms, attribute); } - static String toMinecraft(String bukkit) { + public static String toMinecraft(String bukkit) { int first = bukkit.indexOf('_'); int second = bukkit.indexOf('_', first + 1); @@ -36,4 +39,17 @@ public class CraftAttributeMap implements Attributable { return sb.toString(); } + + public static String toMinecraft(Attribute attribute) { + return toMinecraft(attribute.name()); + } + + public static Attribute fromMinecraft(String nms) { + String[] split = nms.split("\\.", 2); + + String generic = split[0]; + String descriptor = CaseFormat.LOWER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, split[1]); // movementSpeed -> MOVEMENT_SPEED + String fin = generic + "_" + descriptor; + return EnumUtils.getEnum(Attribute.class, fin.toUpperCase(Locale.ROOT)); // so we can return null without throwing exceptions + } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index c0ea341e26..63cd6ba301 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -9,10 +9,20 @@ import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.NoSuchElementException; +import com.google.common.base.Preconditions; +import com.google.common.base.Strings; +import com.google.common.collect.HashMultimap; +import com.google.common.collect.ImmutableMultimap; +import com.google.common.collect.Multimap; +import com.google.common.collect.SetMultimap; +import net.minecraft.server.EnumItemSlot; +import net.minecraft.server.GenericAttributes; import net.minecraft.server.IChatBaseComponent; import net.minecraft.server.NBTBase; import net.minecraft.server.NBTTagCompound; @@ -20,22 +30,28 @@ import net.minecraft.server.NBTTagList; import net.minecraft.server.NBTTagString; import org.apache.commons.lang.Validate; +import org.apache.commons.lang3.EnumUtils; import org.bukkit.Material; +import org.bukkit.attribute.Attribute; +import org.bukkit.attribute.AttributeModifier; import org.bukkit.configuration.serialization.ConfigurationSerializable; import org.bukkit.configuration.serialization.DelegateDeserialization; import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.craftbukkit.CraftEquipmentSlot; import org.bukkit.craftbukkit.Overridden; +import org.bukkit.craftbukkit.attribute.CraftAttributeInstance; +import org.bukkit.craftbukkit.attribute.CraftAttributeMap; import org.bukkit.craftbukkit.inventory.CraftMetaItem.ItemMetaKey.Specific; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.bukkit.enchantments.Enchantment; +import org.bukkit.inventory.EquipmentSlot; import org.bukkit.inventory.ItemFlag; import org.bukkit.inventory.meta.Damageable; import org.bukkit.inventory.meta.ItemMeta; import org.bukkit.inventory.meta.Repairable; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; @@ -45,15 +61,16 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; -import java.util.HashSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; -import net.minecraft.server.ChatComponentText; import net.minecraft.server.EnumChatFormat; import net.minecraft.server.NBTCompressedStreamTools; import org.apache.commons.codec.binary.Base64; +import javax.annotation.Nonnull; +import javax.annotation.Nullable; + /** * Children must include the following: * @@ -206,8 +223,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { @Specific(Specific.To.NBT) static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl"); static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost"); - @Specific(Specific.To.NBT) - static final ItemMetaKey ATTRIBUTES = new ItemMetaKey("AttributeModifiers"); + static final ItemMetaKey ATTRIBUTES = new ItemMetaKey("AttributeModifiers", "attribute-modifiers"); @Specific(Specific.To.NBT) static final ItemMetaKey ATTRIBUTES_IDENTIFIER = new ItemMetaKey("AttributeName"); @Specific(Specific.To.NBT) @@ -221,6 +237,8 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { @Specific(Specific.To.NBT) static final ItemMetaKey ATTRIBUTES_UUID_LOW = new ItemMetaKey("UUIDLeast"); @Specific(Specific.To.NBT) + static final ItemMetaKey ATTRIBUTES_SLOT = new ItemMetaKey("Slot"); + @Specific(Specific.To.NBT) static final ItemMetaKey HIDEFLAGS = new ItemMetaKey("HideFlags", "ItemFlags"); @Specific(Specific.To.NBT) static final ItemMetaKey UNBREAKABLE = new ItemMetaKey("Unbreakable"); @@ -231,6 +249,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { private IChatBaseComponent locName; private List lore; private Map enchantments; + private Multimap attributeModifiers; private int repairCost; private int hideFlag; private boolean unbreakable; @@ -257,6 +276,10 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { this.enchantments = new HashMap(meta.enchantments); } + if (meta.hasAttributeModifiers()) { + this.attributeModifiers = HashMultimap.create(meta.attributeModifiers); + } + this.repairCost = meta.repairCost; this.hideFlag = meta.hideFlag; this.unbreakable = meta.unbreakable; @@ -301,6 +324,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { } this.enchantments = buildEnchantments(tag, ENCHANTMENTS); + this.attributeModifiers = buildModifiers(tag, ATTRIBUTES); if (tag.hasKey(REPAIR.NBT)) { repairCost = tag.getInt(REPAIR.NBT); @@ -316,52 +340,6 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { damage = tag.getInt(DAMAGE.NBT); } - if (tag.get(ATTRIBUTES.NBT) instanceof NBTTagList) { - NBTTagList save = null; - NBTTagList nbttaglist = tag.getList(ATTRIBUTES.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); - - for (int i = 0; i < nbttaglist.size(); ++i) { - if (!(nbttaglist.get(i) instanceof NBTTagCompound)) { - continue; - } - NBTTagCompound nbttagcompound = (NBTTagCompound) nbttaglist.get(i); - - if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_UUID_HIGH.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { - continue; - } - if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_UUID_LOW.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { - continue; - } - if (!(nbttagcompound.get(ATTRIBUTES_IDENTIFIER.NBT) instanceof NBTTagString) || !CraftItemFactory.KNOWN_NBT_ATTRIBUTE_NAMES.contains(nbttagcompound.getString(ATTRIBUTES_IDENTIFIER.NBT))) { - continue; - } - if (!(nbttagcompound.get(ATTRIBUTES_NAME.NBT) instanceof NBTTagString) || nbttagcompound.getString(ATTRIBUTES_NAME.NBT).isEmpty()) { - continue; - } - if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_VALUE.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER)) { - continue; - } - if (!nbttagcompound.hasKeyOfType(ATTRIBUTES_TYPE.NBT, CraftMagicNumbers.NBT.TAG_ANY_NUMBER) || nbttagcompound.getInt(ATTRIBUTES_TYPE.NBT) < 0 || nbttagcompound.getInt(ATTRIBUTES_TYPE.NBT) > 2) { - continue; - } - - if (save == null) { - save = new NBTTagList(); - } - - NBTTagCompound entry = new NBTTagCompound(); - entry.set(ATTRIBUTES_UUID_HIGH.NBT, nbttagcompound.get(ATTRIBUTES_UUID_HIGH.NBT)); - entry.set(ATTRIBUTES_UUID_LOW.NBT, nbttagcompound.get(ATTRIBUTES_UUID_LOW.NBT)); - entry.set(ATTRIBUTES_IDENTIFIER.NBT, nbttagcompound.get(ATTRIBUTES_IDENTIFIER.NBT)); - entry.set(ATTRIBUTES_NAME.NBT, nbttagcompound.get(ATTRIBUTES_NAME.NBT)); - entry.set(ATTRIBUTES_VALUE.NBT, nbttagcompound.get(ATTRIBUTES_VALUE.NBT)); - entry.set(ATTRIBUTES_TYPE.NBT, nbttagcompound.get(ATTRIBUTES_TYPE.NBT)); - save.add(entry); - } - - unhandledTags.put(ATTRIBUTES.NBT, save); - } - Set keys = tag.getKeys(); for (String key : keys) { if (!getHandledTags().contains(key)) { @@ -391,6 +369,45 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { return enchantments; } + static Multimap buildModifiers(NBTTagCompound tag, ItemMetaKey key) { + Multimap modifiers = HashMultimap.create(); + if (!tag.hasKey(key.NBT)) { + return modifiers; + } + NBTTagList mods = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); + int size = mods.size(); + + for (int i = 0; i < size; i++) { + NBTTagCompound entry = mods.getCompound(i); + if (entry.isEmpty()) { + // entry is not an actual NBTTagCompound. getCompound returns empty NBTTagCompound in that case + continue; + } + net.minecraft.server.AttributeModifier nmsModifier = GenericAttributes.a(entry); + Preconditions.checkNotNull(nmsModifier, "Could not create AttributeModifier. %s", entry.toString()); + + AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); + + String attributeName = entry.getString(ATTRIBUTES_IDENTIFIER.NBT); + Preconditions.checkArgument(!Strings.isNullOrEmpty(attributeName), "Missing Attribute for AttributeModifier. %s", entry.toString()); + + Attribute attribute = CraftAttributeMap.fromMinecraft(attributeName); + Preconditions.checkNotNull(attribute, "Could not convert to Bukkit Attribute. %s", attributeName); + + if (entry.hasKeyOfType(ATTRIBUTES_SLOT.NBT, CraftMagicNumbers.NBT.TAG_STRING)) { + String slotName = entry.getString(ATTRIBUTES_SLOT.NBT); + Preconditions.checkArgument(!Strings.isNullOrEmpty(slotName), "Missing Slot when Slot is specified. %s", entry.toString()); + + EquipmentSlot slot = CraftEquipmentSlot.getSlot(EnumItemSlot.a(slotName.toLowerCase(Locale.ROOT))); // PAIL rename fromName + Preconditions.checkNotNull(slot, "No Slot found when Slot was specified. %s", entry.toString()); + + attribMod = new AttributeModifier(attribMod.getUniqueId(), attribMod.getName(), attribMod.getAmount(), attribMod.getOperation(), slot); + } + modifiers.put(attribute, attribMod); + } + return modifiers; + } + CraftMetaItem(Map map) { setDisplayName(SerializableMeta.getString(map, NAME.BUKKIT, true)); setLocalizedName(SerializableMeta.getString(map, LOCNAME.BUKKIT, true)); @@ -401,6 +418,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { } enchantments = buildEnchantments(map, ENCHANTMENTS); + attributeModifiers = buildModifiers(map, ATTRIBUTES); Integer repairCost = SerializableMeta.getObject(Integer.class, map, REPAIR.BUKKIT, true); if (repairCost != null) { @@ -474,6 +492,42 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { return enchantments; } + static Multimap buildModifiers(Map map, ItemMetaKey key) { + Map mods = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); + Multimap result = HashMultimap.create(); + if (mods == null) { + return result; + } + + for (Object obj : mods.keySet()) { + if (!(obj instanceof String)) { + continue; + } + String attributeName = (String) obj; + if (Strings.isNullOrEmpty(attributeName)) { + continue; + } + List list = SerializableMeta.getObject(List.class, mods, attributeName, true); + if (list == null || list.isEmpty()) { + return result; + } + + for (Object o : list) { + if (!(o instanceof AttributeModifier)) { // this catches null + continue; + } + AttributeModifier modifier = (AttributeModifier) o; + Attribute attribute = EnumUtils.getEnum(Attribute.class, attributeName.toUpperCase(Locale.ROOT)); + if (attribute == null) { + continue; + } + + result.put(attribute, modifier); + } + } + return result; + } + @Overridden void applyToItem(NBTTagCompound itemTag) { if (hasDisplayName()) { @@ -492,6 +546,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { } applyEnchantments(enchantments, itemTag, ENCHANTMENTS); + applyModifiers(attributeModifiers, itemTag, ATTRIBUTES); if (hasRepairCost()) { itemTag.setInt(REPAIR.NBT, repairCost); @@ -542,6 +597,35 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { tag.set(key.NBT, list); } + static void applyModifiers(Multimap modifiers, NBTTagCompound tag, ItemMetaKey key) { + if (modifiers == null || modifiers.isEmpty()) { + return; + } + + NBTTagList list = new NBTTagList(); + for (Map.Entry entry : modifiers.entries()) { + if (entry.getKey() == null || entry.getValue() == null) { + continue; + } + net.minecraft.server.AttributeModifier nmsModifier = CraftAttributeInstance.convert(entry.getValue()); + NBTTagCompound sub = GenericAttributes.a(nmsModifier); + Preconditions.checkState(!sub.isEmpty(), "Could not convert AttributeModifier. It was supplied in an invalid format. The following was supplied: %s", sub.toString()); + + String name = CraftAttributeMap.toMinecraft(entry.getKey()); + Preconditions.checkArgument(!Strings.isNullOrEmpty(name), "Could not convert to Bukkit Attribute. %s", entry.getKey().name()); + + sub.setString(ATTRIBUTES_IDENTIFIER.NBT, name); // Attribute Name + if (entry.getValue().getSlot() != null) { + EnumItemSlot slot = CraftEquipmentSlot.getNMS(entry.getValue().getSlot()); + if (slot != null) { + sub.setString(ATTRIBUTES_SLOT.NBT, slot.d()); // PAIL rename getSlotName, getName + } + } + list.add(sub); + } + tag.set(key.NBT, list); + } + void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) { final NBTTagCompound display = tag.getCompound(DISPLAY.NBT); @@ -559,7 +643,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { @Overridden boolean isEmpty() { - return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage()); + return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); } public String getDisplayName() { @@ -713,6 +797,136 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { this.unbreakable = unbreakable; } + @Override + public boolean hasAttributeModifiers() { + return attributeModifiers != null && !attributeModifiers.isEmpty(); + } + + @Override + public Multimap getAttributeModifiers() { + return hasAttributeModifiers() ? ImmutableMultimap.copyOf(attributeModifiers) : null; + } + + private void checkAttributeList() { + if (attributeModifiers == null) { + attributeModifiers = HashMultimap.create(); + } + } + + @Override + public Multimap getAttributeModifiers(@Nullable EquipmentSlot slot) { + checkAttributeList(); + SetMultimap result = HashMultimap.create(); + for (Map.Entry entry : attributeModifiers.entries()) { + if (entry.getValue().getSlot() == null || entry.getValue().getSlot() == slot) { + result.put(entry.getKey(), entry.getValue()); + } + } + return result; + } + + @Override + public Collection getAttributeModifiers(@Nonnull Attribute attribute) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); + return attributeModifiers.containsKey(attribute) ? ImmutableList.copyOf(attributeModifiers.get(attribute)) : null; + } + + @Override + public boolean addAttributeModifier(@Nonnull Attribute attribute, @Nonnull AttributeModifier modifier) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); + Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null"); + checkAttributeList(); + for (Map.Entry entry : attributeModifiers.entries()) { + Preconditions.checkArgument(!entry.getValue().getUniqueId().equals(modifier.getUniqueId()), "Cannot register AttributeModifier. Modifier is already applied! %s", modifier); + } + return attributeModifiers.put(attribute, modifier); + } + + @Override + public void setAttributeModifiers(@Nullable Multimap attributeModifiers) { + if (attributeModifiers == null || attributeModifiers.isEmpty()) { + this.attributeModifiers = HashMultimap.create(); + return; + } + Iterator> iterator = attributeModifiers.entries().iterator(); + this.attributeModifiers.clear(); + while (iterator.hasNext()) { + Map.Entry next = iterator.next(); + + if (next.getKey() == null || next.getValue() == null) { + iterator.remove(); + continue; + } + this.attributeModifiers.put(next.getKey(), next.getValue()); + } + } + + @Override + public boolean removeAttributeModifier(@Nonnull Attribute attribute) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); + checkAttributeList(); + return !attributeModifiers.removeAll(attribute).isEmpty(); + } + + @Override + public boolean removeAttributeModifier(@Nullable EquipmentSlot slot) { + checkAttributeList(); + int removed = 0; + Iterator> iter = attributeModifiers.entries().iterator(); + + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + // Explicitly match against null because (as of MC 1.13) AttributeModifiers without a - + // set slot are active in any slot. + if (entry.getValue().getSlot() == null || entry.getValue().getSlot() == slot) { + iter.remove(); + ++removed; + } + } + return removed > 0; + } + + @Override + public boolean removeAttributeModifier(@Nonnull Attribute attribute, @Nonnull AttributeModifier modifier) { + Preconditions.checkNotNull(attribute, "Attribute cannot be null"); + Preconditions.checkNotNull(modifier, "AttributeModifier cannot be null"); + checkAttributeList(); + int removed = 0; + Iterator> iter = attributeModifiers.entries().iterator(); + + while (iter.hasNext()) { + Map.Entry entry = iter.next(); + if (entry.getKey() == null || entry.getValue() == null) { + iter.remove(); + ++removed; + continue; // remove all null values while we are here + } + + if (entry.getKey() == attribute && entry.getValue().getUniqueId().equals(modifier.getUniqueId())) { + iter.remove(); + ++removed; + } + } + return removed > 0; + } + + private static boolean compareModifiers(Multimap first, Multimap second) { + if (first == null || second == null) { + return false; + } + for (Map.Entry entry : first.entries()) { + if (!second.containsEntry(entry.getKey(), entry.getValue())) { + return false; + } + } + for (Map.Entry entry : second.entries()) { + if (!first.containsEntry(entry.getKey(), entry.getValue())) { + return false; + } + } + return true; + } + @Override public boolean hasDamage() { return damage > 0; @@ -754,6 +968,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { && (this.hasEnchants() ? that.hasEnchants() && this.enchantments.equals(that.enchantments) : !that.hasEnchants()) && (this.hasLore() ? that.hasLore() && this.lore.equals(that.lore) : !that.hasLore()) && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()) + && (this.hasAttributeModifiers() ? that.hasAttributeModifiers() && compareModifiers(this.attributeModifiers, that.attributeModifiers) : !that.hasAttributeModifiers()) && (this.unhandledTags.equals(that.unhandledTags)) && (this.hideFlag == that.hideFlag) && (this.isUnbreakable() == that.isUnbreakable()) @@ -787,6 +1002,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { hash = 61 * hash + hideFlag; hash = 61 * hash + (isUnbreakable() ? 1231 : 1237); hash = 61 * hash + (hasDamage() ? this.damage : 0); + hash = 61 * hash + (hasAttributeModifiers() ? this.attributeModifiers.hashCode() : 0); return hash; } @@ -801,6 +1017,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { if (this.enchantments != null) { clone.enchantments = new HashMap(this.enchantments); } + if (this.hasAttributeModifiers()) { + clone.attributeModifiers = HashMultimap.create(this.attributeModifiers); + } clone.hideFlag = this.hideFlag; clone.unbreakable = this.unbreakable; clone.damage = this.damage; @@ -831,6 +1050,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { } serializeEnchantments(enchantments, builder, ENCHANTMENTS); + serializeModifiers(attributeModifiers, builder, ATTRIBUTES); if (hasRepairCost()) { builder.put(REPAIR.BUKKIT, repairCost); @@ -891,6 +1111,25 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { builder.put(key.BUKKIT, enchants.build()); } + static void serializeModifiers(Multimap modifiers, ImmutableMap.Builder builder, ItemMetaKey key) { + if (modifiers == null || modifiers.isEmpty()) { + return; + } + + Map> mods = new HashMap<>(); + for (Map.Entry entry : modifiers.entries()) { + if (entry.getKey() == null) { + continue; + } + Collection modCollection = modifiers.get(entry.getKey()); + if (modCollection == null || modCollection.isEmpty()) { + continue; + } + mods.put(entry.getKey().name(), new ArrayList<>(modCollection)); + } + builder.put(key.BUKKIT, mods); + } + static void safelyAdd(Iterable addFrom, Collection addTo, int maxItemLength) { if (addFrom == null) { return; @@ -944,6 +1183,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable { HIDEFLAGS.NBT, UNBREAKABLE.NBT, DAMAGE.NBT, + ATTRIBUTES.NBT, + ATTRIBUTES_IDENTIFIER.NBT, + ATTRIBUTES_NAME.NBT, + ATTRIBUTES_VALUE.NBT, + ATTRIBUTES_UUID_HIGH.NBT, + ATTRIBUTES_UUID_LOW.NBT, + ATTRIBUTES_SLOT.NBT, CraftMetaMap.MAP_SCALING.NBT, CraftMetaMap.MAP_ID.NBT, CraftMetaPotion.POTION_EFFECTS.NBT,