From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Aikar Date: Thu, 28 May 2015 23:00:19 -0400 Subject: [PATCH] Handle Item Meta Inconsistencies First, Enchantment order would blow away seeing 2 items as the same, however the Client forces enchantment list in a certain order, as well as does the /enchant command. Anvils can insert it into forced order, causing 2 same items to be considered different. This change makes unhandled NBT Tags and Enchantments use a sorted tree map, so they will always be in a consistent order. Additionally, the old enchantment API was never updated when ItemMeta was added, resulting in 2 different ways to modify an items enchantments. For consistency, the old API methods now forward to use the ItemMeta API equivalents, and should deprecate the old API's. diff --git a/src/main/java/net/minecraft/world/item/ItemStack.java b/src/main/java/net/minecraft/world/item/ItemStack.java index 6291265ae4691bf7dffe196d20571c1c30e8d906..70511628eefc28163d07f50f18d9cc55dd93d68b 100644 --- a/src/main/java/net/minecraft/world/item/ItemStack.java +++ b/src/main/java/net/minecraft/world/item/ItemStack.java @@ -181,6 +181,23 @@ public final class ItemStack { return this.getItem().getTooltipImage(this); } + // Paper start + private static final java.util.Comparator enchantSorter = java.util.Comparator.comparing(o -> o.getString("id")); + private void processEnchantOrder(@Nullable CompoundTag tag) { + if (tag == null || !tag.contains("Enchantments", net.minecraft.nbt.Tag.TAG_LIST)) { + return; + } + ListTag list = tag.getList("Enchantments", net.minecraft.nbt.Tag.TAG_COMPOUND); + if (list.size() < 2) { + return; + } + try { + //noinspection unchecked + list.sort((java.util.Comparator) enchantSorter); // Paper + } catch (Exception ignored) {} + } + // Paper end + public ItemStack(ItemLike item) { this(item, 1); } @@ -227,6 +244,7 @@ public final class ItemStack { this.count = nbttagcompound.getByte("Count"); if (nbttagcompound.contains("tag", 10)) { this.tag = nbttagcompound.getCompound("tag").copy(); + this.processEnchantOrder(this.tag); // Paper this.getItem().verifyTagAfterLoad(this.tag); } @@ -846,6 +864,7 @@ public final class ItemStack { public void setTag(@Nullable CompoundTag nbt) { this.tag = nbt; + this.processEnchantOrder(this.tag); // Paper if (this.getItem().canBeDepleted()) { this.setDamageValue(this.getDamageValue()); } @@ -1143,6 +1162,7 @@ public final class ItemStack { ListTag nbttaglist = this.tag.getList("Enchantments", 10); nbttaglist.add(EnchantmentHelper.storeEnchantment(EnchantmentHelper.getEnchantmentId(enchantment), (byte) level)); + processEnchantOrder(this.tag); // Paper } public boolean isEnchanted() { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 0bb01e53f1c15071c8cd818cce79af8196fe790f..b59e84bbed37b002a34fe81efdce6f025617fc84 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -192,28 +192,11 @@ public final class CraftItemStack extends ItemStack { public void addUnsafeEnchantment(Enchantment ench, int level) { Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); - if (!CraftItemStack.makeTag(this.handle)) { - return; - } - ListTag list = CraftItemStack.getEnchantmentList(this.handle); - if (list == null) { - list = new ListTag(); - this.handle.getTag().put(ENCHANTMENTS.NBT, list); - } - int size = list.size(); - - for (int i = 0; i < size; i++) { - CompoundTag tag = (CompoundTag) list.get(i); - String id = tag.getString(ENCHANTMENTS_ID.NBT); - if (ench.getKey().equals(NamespacedKey.fromString(id))) { - tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); - return; - } - } - CompoundTag tag = new CompoundTag(); - tag.putString(ENCHANTMENTS_ID.NBT, ench.getKey().toString()); - tag.putShort(ENCHANTMENTS_LVL.NBT, (short) level); - list.add(tag); + // Paper start - Replace whole method + final ItemMeta itemMeta = this.getItemMeta(); + itemMeta.addEnchant(ench, level, true); + this.setItemMeta(itemMeta); + // Paper end } static boolean makeTag(net.minecraft.world.item.ItemStack item) { @@ -246,43 +229,15 @@ public final class CraftItemStack extends ItemStack { public int removeEnchantment(Enchantment ench) { Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); - ListTag list = CraftItemStack.getEnchantmentList(this.handle), listCopy; - if (list == null) { - return 0; - } - int index = Integer.MIN_VALUE; - int level = Integer.MIN_VALUE; - int size = list.size(); - - for (int i = 0; i < size; i++) { - CompoundTag enchantment = (CompoundTag) list.get(i); - String id = enchantment.getString(ENCHANTMENTS_ID.NBT); - if (ench.getKey().equals(NamespacedKey.fromString(id))) { - index = i; - level = 0xffff & enchantment.getShort(ENCHANTMENTS_LVL.NBT); - break; - } - } - - if (index == Integer.MIN_VALUE) { - return 0; - } - if (size == 1) { - this.handle.getTag().remove(ENCHANTMENTS.NBT); - if (this.handle.getTag().isEmpty()) { - this.handle.setTag(null); - } - return level; - } - - // This is workaround for not having an index removal - listCopy = new ListTag(); - for (int i = 0; i < size; i++) { - if (i != index) { - listCopy.add(list.get(i)); - } + // Paper start - replace entire method + int level = getEnchantmentLevel(ench); + if (level > 0) { + final ItemMeta itemMeta = this.getItemMeta(); + if (itemMeta == null) return 0; + itemMeta.removeEnchant(ench); + this.setItemMeta(itemMeta); } - this.handle.getTag().put(ENCHANTMENTS.NBT, listCopy); + // Paper end return level; } @@ -294,7 +249,7 @@ public final class CraftItemStack extends ItemStack { @Override public Map getEnchantments() { - return CraftItemStack.getEnchantments(this.handle); + return this.hasItemMeta() ? this.getItemMeta().getEnchants() : ImmutableMap.of(); // Paper - use Item Meta } static Map getEnchantments(net.minecraft.world.item.ItemStack item) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index ffdea312f93d00289364ef4d41a820cd1338f3bd..361268bcc0197c2f9f4bd065d8f7b51771d562a9 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -6,6 +6,7 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.LinkedHashMultimap; +import com.google.common.collect.ImmutableSortedMap; // Paper import com.google.common.collect.Lists; import com.google.common.collect.Multimap; import com.google.common.collect.SetMultimap; @@ -23,6 +24,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Base64; import java.util.Collection; +import java.util.Comparator; // Paper import java.util.EnumSet; import java.util.HashMap; import java.util.Iterator; @@ -33,6 +35,7 @@ import java.util.Map; import java.util.NoSuchElementException; import java.util.Objects; import java.util.Set; +import java.util.TreeMap; // Paper import java.util.logging.Level; import java.util.logging.Logger; import javax.annotation.Nonnull; @@ -277,7 +280,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { private List lore; // null and empty are two different states internally private Integer customModelData; private CompoundTag blockData; - private Map enchantments; + private EnchantmentMap enchantments; // Paper private Multimap attributeModifiers; private int repairCost; private int hideFlag; @@ -288,7 +291,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { private static final CraftPersistentDataTypeRegistry DATA_TYPE_REGISTRY = new CraftPersistentDataTypeRegistry(); private CompoundTag internalTag; - final Map unhandledTags = new HashMap(); // Visible for testing only + final Map unhandledTags = new TreeMap(); // Visible for testing only // Paper private CraftPersistentDataContainer persistentDataContainer = new CraftPersistentDataContainer(CraftMetaItem.DATA_TYPE_REGISTRY); private int version = CraftMagicNumbers.INSTANCE.getDataVersion(); // Internal use only @@ -309,7 +312,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { this.blockData = meta.blockData; if (meta.enchantments != null) { - this.enchantments = new LinkedHashMap(meta.enchantments); + this.enchantments = new EnchantmentMap(meta.enchantments); // Paper } if (meta.hasAttributeModifiers()) { @@ -392,13 +395,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } } - static Map buildEnchantments(CompoundTag tag, ItemMetaKey key) { + static EnchantmentMap buildEnchantments(CompoundTag tag, ItemMetaKey key) { // Paper if (!tag.contains(key.NBT)) { return null; } ListTag ench = tag.getList(key.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND); - Map enchantments = new LinkedHashMap(ench.size()); + EnchantmentMap enchantments = new EnchantmentMap(); // Paper for (int i = 0; i < ench.size(); i++) { String id = ((CompoundTag) ench.get(i)).getString(CraftMetaItem.ENCHANTMENTS_ID.NBT); @@ -551,13 +554,13 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { } } - static Map buildEnchantments(Map map, ItemMetaKey key) { + static EnchantmentMap buildEnchantments(Map map, ItemMetaKey key) { // Paper Map ench = SerializableMeta.getObject(Map.class, map, key.BUKKIT, true); if (ench == null) { return null; } - Map enchantments = new LinkedHashMap(ench.size()); + EnchantmentMap enchantments = new EnchantmentMap(); // Paper for (Map.Entry entry : ench.entrySet()) { // Doctor older enchants String enchantKey = entry.getKey().toString(); @@ -845,14 +848,14 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { @Override public Map getEnchants() { - return this.hasEnchants() ? ImmutableMap.copyOf(this.enchantments) : ImmutableMap.of(); + return this.hasEnchants() ? ImmutableSortedMap.copyOfSorted(this.enchantments) : ImmutableMap.of(); // Paper } @Override public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { Preconditions.checkArgument(ench != null, "Enchantment cannot be null"); if (this.enchantments == null) { - this.enchantments = new LinkedHashMap(4); + this.enchantments = new EnchantmentMap(); // Paper } if (ignoreRestrictions || level >= ench.getStartLevel() && level <= ench.getMaxLevel()) { @@ -1269,7 +1272,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { clone.customModelData = this.customModelData; clone.blockData = this.blockData; if (this.enchantments != null) { - clone.enchantments = new LinkedHashMap(this.enchantments); + clone.enchantments = new EnchantmentMap(this.enchantments); // Paper } if (this.hasAttributeModifiers()) { clone.attributeModifiers = LinkedHashMultimap.create(this.attributeModifiers); @@ -1516,4 +1519,22 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta { return CraftMetaItem.HANDLED_TAGS; } } + + // Paper start + private static class EnchantmentMap extends TreeMap { + private EnchantmentMap(Map enchantments) { + this(); + putAll(enchantments); + } + + private EnchantmentMap() { + super(Comparator.comparing(o -> o.getKey().toString())); + } + + public EnchantmentMap clone() { + return (EnchantmentMap) super.clone(); + } + } + // Paper end + }