package org.bukkit.craftbukkit.inventory; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.lang.reflect.Constructor; 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; 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.inventory.tags.CraftCustomItemTagContainer; import org.bukkit.craftbukkit.util.CraftChatMessage; import org.bukkit.craftbukkit.util.CraftMagicNumbers; import org.bukkit.craftbukkit.util.CraftNBTTagConfigSerializer; 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 org.bukkit.inventory.meta.tags.CustomItemTagContainer; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Sets; import com.google.gson.JsonParseException; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.Arrays; import java.util.EnumSet; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; 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: * *
  • Constructor(CraftMetaItem meta) *
  • Constructor(NBTTagCompound tag) *
  • Constructor(Map map) *

    *
  • void applyToItem(NBTTagCompound tag) *
  • boolean applicableTo(Material type) *

    *
  • boolean equalsCommon(CraftMetaItem meta) *
  • boolean notUncommon(CraftMetaItem meta) *

    *
  • boolean isEmpty() *
  • boolean is{Type}Empty() *

    *
  • int applyHash() *
  • public Class clone() *

    *
  • Builder serialize(Builder builder) *
  • SerializableMeta.Deserializers deserializer() */ @DelegateDeserialization(CraftMetaItem.SerializableMeta.class) class CraftMetaItem implements ItemMeta, Damageable, Repairable { static class ItemMetaKey { @Retention(RetentionPolicy.SOURCE) @Target(ElementType.FIELD) @interface Specific { enum To { BUKKIT, NBT, ; } To value(); } final String BUKKIT; final String NBT; ItemMetaKey(final String both) { this(both, both); } ItemMetaKey(final String nbt, final String bukkit) { this.NBT = nbt; this.BUKKIT = bukkit; } } @SerializableAs("ItemMeta") public static class SerializableMeta implements ConfigurationSerializable { static final String TYPE_FIELD = "meta-type"; static final ImmutableMap, String> classMap; static final ImmutableMap> constructorMap; static { classMap = ImmutableMap., String>builder() .put(CraftMetaBanner.class, "BANNER") .put(CraftMetaBlockState.class, "TILE_ENTITY") .put(CraftMetaBook.class, "BOOK") .put(CraftMetaBookSigned.class, "BOOK_SIGNED") .put(CraftMetaSkull.class, "SKULL") .put(CraftMetaLeatherArmor.class, "LEATHER_ARMOR") .put(CraftMetaMap.class, "MAP") .put(CraftMetaPotion.class, "POTION") .put(CraftMetaSpawnEgg.class, "SPAWN_EGG") .put(CraftMetaEnchantedBook.class, "ENCHANTED") .put(CraftMetaFirework.class, "FIREWORK") .put(CraftMetaCharge.class, "FIREWORK_EFFECT") .put(CraftMetaKnowledgeBook.class, "KNOWLEDGE_BOOK") .put(CraftMetaTropicalFishBucket.class, "TROPICAL_FISH_BUCKET") .put(CraftMetaItem.class, "UNSPECIFIC") .build(); final ImmutableMap.Builder> classConstructorBuilder = ImmutableMap.builder(); for (Map.Entry, String> mapping : classMap.entrySet()) { try { classConstructorBuilder.put(mapping.getValue(), mapping.getKey().getDeclaredConstructor(Map.class)); } catch (NoSuchMethodException e) { throw new AssertionError(e); } } constructorMap = classConstructorBuilder.build(); } private SerializableMeta() { } public static ItemMeta deserialize(Map map) throws Throwable { Validate.notNull(map, "Cannot deserialize null map"); String type = getString(map, TYPE_FIELD, false); Constructor constructor = constructorMap.get(type); if (constructor == null) { throw new IllegalArgumentException(type + " is not a valid " + TYPE_FIELD); } try { return constructor.newInstance(map); } catch (final InstantiationException e) { throw new AssertionError(e); } catch (final IllegalAccessException e) { throw new AssertionError(e); } catch (final InvocationTargetException e) { throw e.getCause(); } } public Map serialize() { throw new AssertionError(); } static String getString(Map map, Object field, boolean nullable) { return getObject(String.class, map, field, nullable); } static boolean getBoolean(Map map, Object field) { Boolean value = getObject(Boolean.class, map, field, true); return value != null && value; } static T getObject(Class clazz, Map map, Object field, boolean nullable) { final Object object = map.get(field); if (clazz.isInstance(object)) { return clazz.cast(object); } if (object == null) { if (!nullable) { throw new NoSuchElementException(map + " does not contain " + field); } return null; } throw new IllegalArgumentException(field + "(" + object + ") is not a valid " + clazz); } } static final ItemMetaKey NAME = new ItemMetaKey("Name", "display-name"); static final ItemMetaKey LOCNAME = new ItemMetaKey("LocName", "loc-name"); @Specific(Specific.To.NBT) static final ItemMetaKey DISPLAY = new ItemMetaKey("display"); static final ItemMetaKey LORE = new ItemMetaKey("Lore", "lore"); static final ItemMetaKey ENCHANTMENTS = new ItemMetaKey("Enchantments", "enchants"); @Specific(Specific.To.NBT) static final ItemMetaKey ENCHANTMENTS_ID = new ItemMetaKey("id"); @Specific(Specific.To.NBT) static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl"); static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost"); 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) static final ItemMetaKey ATTRIBUTES_NAME = new ItemMetaKey("Name"); @Specific(Specific.To.NBT) static final ItemMetaKey ATTRIBUTES_VALUE = new ItemMetaKey("Amount"); @Specific(Specific.To.NBT) static final ItemMetaKey ATTRIBUTES_TYPE = new ItemMetaKey("Operation"); @Specific(Specific.To.NBT) static final ItemMetaKey ATTRIBUTES_UUID_HIGH = new ItemMetaKey("UUIDMost"); @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"); @Specific(Specific.To.NBT) static final ItemMetaKey DAMAGE = new ItemMetaKey("Damage"); static final ItemMetaKey BUKKIT_CUSTOM_TAG = new ItemMetaKey("PublicBukkitValues"); private IChatBaseComponent displayName; private IChatBaseComponent locName; private List lore; private Map enchantments; private Multimap attributeModifiers; private int repairCost; private int hideFlag; private boolean unbreakable; private int damage; private static final Set HANDLED_TAGS = Sets.newHashSet(); private static final CraftCustomTagTypeRegistry TAG_TYPE_REGISTRY = new CraftCustomTagTypeRegistry(); private NBTTagCompound internalTag; private final Map unhandledTags = new HashMap(); private final CraftCustomItemTagContainer publicItemTagContainer = new CraftCustomItemTagContainer(TAG_TYPE_REGISTRY); CraftMetaItem(CraftMetaItem meta) { if (meta == null) { return; } this.displayName = meta.displayName; this.locName = meta.locName; if (meta.hasLore()) { this.lore = new ArrayList(meta.lore); } if (meta.hasEnchants()) { 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; this.damage = meta.damage; this.unhandledTags.putAll(meta.unhandledTags); this.publicItemTagContainer.putAll(meta.publicItemTagContainer.getRaw()); this.internalTag = meta.internalTag; if (this.internalTag != null) { deserializeInternal(internalTag, meta); } } CraftMetaItem(NBTTagCompound tag) { if (tag.hasKey(DISPLAY.NBT)) { NBTTagCompound display = tag.getCompound(DISPLAY.NBT); if (display.hasKey(NAME.NBT)) { try { displayName = IChatBaseComponent.ChatSerializer.a(display.getString(NAME.NBT)); } catch (JsonParseException ex) { // Ignore (stripped like Vanilla) } } if (display.hasKey(LOCNAME.NBT)) { try { locName = IChatBaseComponent.ChatSerializer.a(display.getString(LOCNAME.NBT)); } catch (JsonParseException ex) { // Ignore (stripped like Vanilla) } } if (display.hasKey(LORE.NBT)) { NBTTagList list = display.getList(LORE.NBT, CraftMagicNumbers.NBT.TAG_STRING); lore = new ArrayList(list.size()); for (int index = 0; index < list.size(); index++) { String line = list.getString(index); lore.add(line); } } } this.enchantments = buildEnchantments(tag, ENCHANTMENTS); this.attributeModifiers = buildModifiers(tag, ATTRIBUTES); if (tag.hasKey(REPAIR.NBT)) { repairCost = tag.getInt(REPAIR.NBT); } if (tag.hasKey(HIDEFLAGS.NBT)) { hideFlag = tag.getInt(HIDEFLAGS.NBT); } if (tag.hasKey(UNBREAKABLE.NBT)) { unbreakable = tag.getBoolean(UNBREAKABLE.NBT); } if (tag.hasKey(DAMAGE.NBT)) { damage = tag.getInt(DAMAGE.NBT); } if (tag.hasKey(BUKKIT_CUSTOM_TAG.NBT)) { NBTTagCompound compound = tag.getCompound(BUKKIT_CUSTOM_TAG.NBT); Set keys = compound.getKeys(); for (String key : keys) { publicItemTagContainer.put(key, compound.get(key)); } } Set keys = tag.getKeys(); for (String key : keys) { if (!getHandledTags().contains(key)) { unhandledTags.put(key, tag.get(key)); } } } static Map buildEnchantments(NBTTagCompound tag, ItemMetaKey key) { if (!tag.hasKey(key.NBT)) { return null; } NBTTagList ench = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); Map enchantments = new HashMap(ench.size()); for (int i = 0; i < ench.size(); i++) { String id = ((NBTTagCompound) ench.get(i)).getString(ENCHANTMENTS_ID.NBT); int level = 0xffff & ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_LVL.NBT); Enchantment enchant = Enchantment.getByKey(CraftNamespacedKey.fromStringOrNull(id)); if (enchant != null) { enchantments.put(enchant, level); } } 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); if (nmsModifier == null) { continue; } AttributeModifier attribMod = CraftAttributeInstance.convert(nmsModifier); String attributeName = entry.getString(ATTRIBUTES_IDENTIFIER.NBT); if (attributeName == null || attributeName.isEmpty()) { continue; } Attribute attribute = CraftAttributeMap.fromMinecraft(attributeName); if (attribute == null) { continue; } if (entry.hasKeyOfType(ATTRIBUTES_SLOT.NBT, CraftMagicNumbers.NBT.TAG_STRING)) { String slotName = entry.getString(ATTRIBUTES_SLOT.NBT); if (slotName == null || slotName.isEmpty()) { modifiers.put(attribute, attribMod); continue; } EquipmentSlot slot = CraftEquipmentSlot.getSlot(EnumItemSlot.a(slotName.toLowerCase(Locale.ROOT))); // PAIL rename fromName if (slot == null) { modifiers.put(attribute, attribMod); continue; } 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)); Iterable lore = SerializableMeta.getObject(Iterable.class, map, LORE.BUKKIT, true); if (lore != null) { safelyAdd(lore, this.lore = new ArrayList(), Integer.MAX_VALUE); } enchantments = buildEnchantments(map, ENCHANTMENTS); attributeModifiers = buildModifiers(map, ATTRIBUTES); Integer repairCost = SerializableMeta.getObject(Integer.class, map, REPAIR.BUKKIT, true); if (repairCost != null) { setRepairCost(repairCost); } Iterable hideFlags = SerializableMeta.getObject(Iterable.class, map, HIDEFLAGS.BUKKIT, true); if (hideFlags != null) { for (Object hideFlagObject : hideFlags) { String hideFlagString = (String) hideFlagObject; try { ItemFlag hideFlatEnum = ItemFlag.valueOf(hideFlagString); addItemFlags(hideFlatEnum); } catch (IllegalArgumentException ex) { // Ignore when we got a old String which does not map to a Enum value anymore } } } Boolean unbreakable = SerializableMeta.getObject(Boolean.class, map, UNBREAKABLE.BUKKIT, true); if (unbreakable != null) { setUnbreakable(unbreakable); } Integer damage = SerializableMeta.getObject(Integer.class, map, DAMAGE.BUKKIT, true); if (damage != null) { setDamage(damage); } String internal = SerializableMeta.getString(map, "internal", true); if (internal != null) { ByteArrayInputStream buf = new ByteArrayInputStream(Base64.decodeBase64(internal)); try { internalTag = NBTCompressedStreamTools.a(buf); deserializeInternal(internalTag, map); Set keys = internalTag.getKeys(); for (String key : keys) { if (!getHandledTags().contains(key)) { unhandledTags.put(key, internalTag.get(key)); } } } catch (IOException ex) { Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); } } Map nbtMap = SerializableMeta.getObject(Map.class, map, BUKKIT_CUSTOM_TAG.BUKKIT, true); if (nbtMap != null) { this.publicItemTagContainer.putAll((NBTTagCompound) CraftNBTTagConfigSerializer.deserialize(nbtMap)); } } void deserializeInternal(NBTTagCompound tag, Object context) { } static Map buildEnchantments(Map map, ItemMetaKey key) { Map ench = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); if (ench == null) { return null; } Map enchantments = new HashMap(ench.size()); for (Map.Entry entry : ench.entrySet()) { // Doctor older enchants String enchantKey = entry.getKey().toString(); if (enchantKey.equals("SWEEPING")) { enchantKey = "SWEEPING_EDGE"; } Enchantment enchantment = Enchantment.getByName(enchantKey); if ((enchantment != null) && (entry.getValue() instanceof Integer)) { enchantments.put(enchantment, (Integer) entry.getValue()); } } 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()) { setDisplayTag(itemTag, NAME.NBT, new NBTTagString(CraftChatMessage.toJSON(displayName))); } if (hasLocalizedName()){ setDisplayTag(itemTag, LOCNAME.NBT, new NBTTagString(CraftChatMessage.toJSON(locName))); } if (hasLore()) { setDisplayTag(itemTag, LORE.NBT, createStringList(lore)); } if (hideFlag != 0) { itemTag.setInt(HIDEFLAGS.NBT, hideFlag); } applyEnchantments(enchantments, itemTag, ENCHANTMENTS); applyModifiers(attributeModifiers, itemTag, ATTRIBUTES); if (hasRepairCost()) { itemTag.setInt(REPAIR.NBT, repairCost); } if (isUnbreakable()) { itemTag.setBoolean(UNBREAKABLE.NBT, unbreakable); } if (hasDamage()) { itemTag.setInt(DAMAGE.NBT, damage); } for (Map.Entry e : unhandledTags.entrySet()) { itemTag.set(e.getKey(), e.getValue()); } if (!publicItemTagContainer.isEmpty()) { NBTTagCompound bukkitCustomCompound = new NBTTagCompound(); Map rawPublicMap = publicItemTagContainer.getRaw(); for (Map.Entry nbtBaseEntry : rawPublicMap.entrySet()) { bukkitCustomCompound.set(nbtBaseEntry.getKey(), nbtBaseEntry.getValue()); } itemTag.set(BUKKIT_CUSTOM_TAG.NBT, bukkitCustomCompound); } } static NBTTagList createStringList(List list) { if (list == null || list.isEmpty()) { return null; } NBTTagList tagList = new NBTTagList(); for (String value : list) { tagList.add(new NBTTagString(value)); } return tagList; } static void applyEnchantments(Map enchantments, NBTTagCompound tag, ItemMetaKey key) { if (enchantments == null || enchantments.size() == 0) { return; } NBTTagList list = new NBTTagList(); for (Map.Entry entry : enchantments.entrySet()) { NBTTagCompound subtag = new NBTTagCompound(); subtag.setString(ENCHANTMENTS_ID.NBT, entry.getKey().getKey().toString()); subtag.setShort(ENCHANTMENTS_LVL.NBT, entry.getValue().shortValue()); list.add(subtag); } 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); if (sub.isEmpty()) { continue; } String name = CraftAttributeMap.toMinecraft(entry.getKey()); if (name == null || name.isEmpty()) { continue; } 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.getSlotName()); } } list.add(sub); } tag.set(key.NBT, list); } void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) { final NBTTagCompound display = tag.getCompound(DISPLAY.NBT); if (!tag.hasKey(DISPLAY.NBT)) { tag.set(DISPLAY.NBT, display); } display.set(key, value); } @Overridden boolean applicableTo(Material type) { return type != Material.AIR; } @Overridden boolean isEmpty() { return !(hasDisplayName() || hasLocalizedName() || hasEnchants() || hasLore() || hasRepairCost() || !unhandledTags.isEmpty() || !publicItemTagContainer.isEmpty() || hideFlag != 0 || isUnbreakable() || hasDamage() || hasAttributeModifiers()); } public String getDisplayName() { return CraftChatMessage.fromComponent(displayName, EnumChatFormat.WHITE); } public final void setDisplayName(String name) { this.displayName = CraftChatMessage.wrapOrNull(name); } public boolean hasDisplayName() { return displayName != null; } @Override public String getLocalizedName() { return CraftChatMessage.fromComponent(locName, EnumChatFormat.WHITE); } @Override public void setLocalizedName(String name) { this.locName = CraftChatMessage.wrapOrNull(name); } @Override public boolean hasLocalizedName() { return locName != null; } public boolean hasLore() { return this.lore != null && !this.lore.isEmpty(); } public boolean hasRepairCost() { return repairCost > 0; } public boolean hasEnchant(Enchantment ench) { Validate.notNull(ench, "Enchantment cannot be null"); return hasEnchants() && enchantments.containsKey(ench); } public int getEnchantLevel(Enchantment ench) { Validate.notNull(ench, "Enchantment cannot be null"); Integer level = hasEnchants() ? enchantments.get(ench) : null; if (level == null) { return 0; } return level; } public Map getEnchants() { return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.of(); } public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { Validate.notNull(ench, "Enchantment cannot be null"); if (enchantments == null) { enchantments = new HashMap(4); } if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { Integer old = enchantments.put(ench, level); return old == null || old != level; } return false; } public boolean removeEnchant(Enchantment ench) { Validate.notNull(ench, "Enchantment cannot be null"); return hasEnchants() && enchantments.remove(ench) != null; } public boolean hasEnchants() { return !(enchantments == null || enchantments.isEmpty()); } public boolean hasConflictingEnchant(Enchantment ench) { return checkConflictingEnchants(enchantments, ench); } @Override public void addItemFlags(ItemFlag... hideFlags) { for (ItemFlag f : hideFlags) { this.hideFlag |= getBitModifier(f); } } @Override public void removeItemFlags(ItemFlag... hideFlags) { for (ItemFlag f : hideFlags) { this.hideFlag &= ~getBitModifier(f); } } @Override public Set getItemFlags() { Set currentFlags = EnumSet.noneOf(ItemFlag.class); for (ItemFlag f : ItemFlag.values()) { if (hasItemFlag(f)) { currentFlags.add(f); } } return currentFlags; } @Override public boolean hasItemFlag(ItemFlag flag) { int bitModifier = getBitModifier(flag); return (this.hideFlag & bitModifier) == bitModifier; } private byte getBitModifier(ItemFlag hideFlag) { return (byte) (1 << hideFlag.ordinal()); } public List getLore() { return this.lore == null ? null : new ArrayList(this.lore); } public void setLore(List lore) { // too tired to think if .clone is better if (lore == null) { this.lore = null; } else { if (this.lore == null) { safelyAdd(lore, this.lore = new ArrayList(lore.size()), Integer.MAX_VALUE); } else { this.lore.clear(); safelyAdd(lore, this.lore, Integer.MAX_VALUE); } } } public int getRepairCost() { return repairCost; } public void setRepairCost(int cost) { // TODO: Does this have limits? repairCost = cost; } @Override public boolean isUnbreakable() { return unbreakable; } @Override public void setUnbreakable(boolean unbreakable) { 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; } @Override public CustomItemTagContainer getCustomTagContainer() { return this.publicItemTagContainer; } 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; } @Override public int getDamage() { return damage; } @Override public void setDamage(int damage) { this.damage = damage; } @Override public final boolean equals(Object object) { if (object == null) { return false; } if (object == this) { return true; } if (!(object instanceof CraftMetaItem)) { return false; } return CraftItemFactory.instance().equals(this, (ItemMeta) object); } /** * This method is almost as weird as notUncommon. * Only return false if your common internals are unequal. * Checking your own internals is redundant if you are not common, as notUncommon is meant for checking those 'not common' variables. */ @Overridden boolean equalsCommon(CraftMetaItem that) { return ((this.hasDisplayName() ? that.hasDisplayName() && this.displayName.equals(that.displayName) : !that.hasDisplayName())) && (this.hasLocalizedName()? that.hasLocalizedName()&& this.locName.equals(that.locName) : !that.hasLocalizedName()) && (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.publicItemTagContainer.equals(that.publicItemTagContainer)) && (this.hideFlag == that.hideFlag) && (this.isUnbreakable() == that.isUnbreakable()) && (this.hasDamage() ? that.hasDamage() && this.damage == that.damage : !that.hasDamage()); } /** * This method is a bit weird... * Return true if you are a common class OR your uncommon parts are empty. * Empty uncommon parts implies the NBT data would be equivalent if both were applied to an item */ @Overridden boolean notUncommon(CraftMetaItem meta) { return true; } @Override public final int hashCode() { return applyHash(); } @Overridden int applyHash() { int hash = 3; hash = 61 * hash + (hasDisplayName() ? this.displayName.hashCode() : 0); hash = 61 * hash + (hasLocalizedName()? this.locName.hashCode() : 0); hash = 61 * hash + (hasLore() ? this.lore.hashCode() : 0); hash = 61 * hash + (hasEnchants() ? this.enchantments.hashCode() : 0); hash = 61 * hash + (hasRepairCost() ? this.repairCost : 0); hash = 61 * hash + unhandledTags.hashCode(); hash = 61 * hash + (!publicItemTagContainer.isEmpty() ? publicItemTagContainer.hashCode() : 0); 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; } @Overridden @Override public CraftMetaItem clone() { try { CraftMetaItem clone = (CraftMetaItem) super.clone(); if (this.lore != null) { clone.lore = new ArrayList(this.lore); } 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; return clone; } catch (CloneNotSupportedException e) { throw new Error(e); } } public final Map serialize() { ImmutableMap.Builder map = ImmutableMap.builder(); map.put(SerializableMeta.TYPE_FIELD, SerializableMeta.classMap.get(getClass())); serialize(map); return map.build(); } @Overridden ImmutableMap.Builder serialize(ImmutableMap.Builder builder) { if (hasDisplayName()) { builder.put(NAME.BUKKIT, CraftChatMessage.fromComponent(displayName)); } if (hasLocalizedName()) { builder.put(LOCNAME.BUKKIT, CraftChatMessage.fromComponent(locName)); } if (hasLore()) { builder.put(LORE.BUKKIT, ImmutableList.copyOf(lore)); } serializeEnchantments(enchantments, builder, ENCHANTMENTS); serializeModifiers(attributeModifiers, builder, ATTRIBUTES); if (hasRepairCost()) { builder.put(REPAIR.BUKKIT, repairCost); } List hideFlags = new ArrayList(); for (ItemFlag hideFlagEnum : getItemFlags()) { hideFlags.add(hideFlagEnum.name()); } if (!hideFlags.isEmpty()) { builder.put(HIDEFLAGS.BUKKIT, hideFlags); } if (isUnbreakable()) { builder.put(UNBREAKABLE.BUKKIT, unbreakable); } if (hasDamage()) { builder.put(DAMAGE.BUKKIT, damage); } final Map internalTags = new HashMap(unhandledTags); serializeInternal(internalTags); if (!internalTags.isEmpty()) { NBTTagCompound internal = new NBTTagCompound(); for (Map.Entry e : internalTags.entrySet()) { internal.set(e.getKey(), e.getValue()); } try { ByteArrayOutputStream buf = new ByteArrayOutputStream(); NBTCompressedStreamTools.a(internal, buf); builder.put("internal", Base64.encodeBase64String(buf.toByteArray())); } catch (IOException ex) { Logger.getLogger(CraftMetaItem.class.getName()).log(Level.SEVERE, null, ex); } } if (!publicItemTagContainer.isEmpty()) { // Store custom tags, wrapped in their compound builder.put(BUKKIT_CUSTOM_TAG.BUKKIT, publicItemTagContainer.serialize()); } return builder; } void serializeInternal(final Map unhandledTags) { } Material updateMaterial(Material material) { return material; } static void serializeEnchantments(Map enchantments, ImmutableMap.Builder builder, ItemMetaKey key) { if (enchantments == null || enchantments.isEmpty()) { return; } ImmutableMap.Builder enchants = ImmutableMap.builder(); for (Map.Entry enchant : enchantments.entrySet()) { enchants.put(enchant.getKey().getName(), enchant.getValue()); } 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; } for (Object object : addFrom) { if (!(object instanceof String)) { if (object != null) { throw new IllegalArgumentException(addFrom + " cannot contain non-string " + object.getClass().getName()); } addTo.add(""); } else { String page = object.toString(); if (page.length() > maxItemLength) { page = page.substring(0, maxItemLength); } addTo.add(page); } } } static boolean checkConflictingEnchants(Map enchantments, Enchantment ench) { if (enchantments == null || enchantments.isEmpty()) { return false; } for (Enchantment enchant : enchantments.keySet()) { if (enchant.conflictsWith(ench)) { return true; } } return false; } @Override public final String toString() { return SerializableMeta.classMap.get(getClass()) + "_META:" + serialize(); // TODO: cry } public static Set getHandledTags() { synchronized (HANDLED_TAGS) { if (HANDLED_TAGS.isEmpty()) { HANDLED_TAGS.addAll(Arrays.asList( DISPLAY.NBT, REPAIR.NBT, ENCHANTMENTS.NBT, HIDEFLAGS.NBT, UNBREAKABLE.NBT, DAMAGE.NBT, BUKKIT_CUSTOM_TAG.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, CraftMetaPotion.DEFAULT_POTION.NBT, CraftMetaPotion.POTION_COLOR.NBT, CraftMetaSkull.SKULL_OWNER.NBT, CraftMetaSkull.SKULL_PROFILE.NBT, CraftMetaSpawnEgg.ENTITY_TAG.NBT, CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, CraftMetaBook.BOOK_TITLE.NBT, CraftMetaBook.BOOK_AUTHOR.NBT, CraftMetaBook.BOOK_PAGES.NBT, CraftMetaBook.RESOLVED.NBT, CraftMetaBook.GENERATION.NBT, CraftMetaFirework.FIREWORKS.NBT, CraftMetaEnchantedBook.STORED_ENCHANTMENTS.NBT, CraftMetaCharge.EXPLOSION.NBT, CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT, CraftMetaKnowledgeBook.BOOK_RECIPES.NBT, CraftMetaTropicalFishBucket.VARIANT.NBT )); } return HANDLED_TAGS; } } }