diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java index 81c6b72d19..1b2394def0 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java @@ -1,5 +1,6 @@ package org.bukkit.craftbukkit.inventory; +import java.util.Collection; import org.apache.commons.lang.Validate; import org.bukkit.Color; @@ -9,13 +10,25 @@ import org.bukkit.inventory.ItemFactory; import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.meta.ItemMeta; +import com.google.common.collect.ImmutableSet; + public final class CraftItemFactory implements ItemFactory { static final Color DEFAULT_LEATHER_COLOR = Color.fromRGB(0xA06540); + static final Collection KNOWN_NBT_ATTRIBUTE_NAMES; private static final CraftItemFactory instance; static { instance = new CraftItemFactory(); ConfigurationSerialization.registerClass(CraftMetaItem.SerializableMeta.class); + KNOWN_NBT_ATTRIBUTE_NAMES = ImmutableSet.builder() + .add("generic.attackDamage") + .add("generic.followRange") + .add("generic.knockbackResistance") + .add("generic.maxHealth") + .add("generic.movementSpeed") + .add("horse.jumpStrength") + .add("zombie.spawnReinforcements") + .build(); } private CraftItemFactory() { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java index 0e0bb32e1e..c22087d171 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -15,7 +15,10 @@ import java.util.NoSuchElementException; import net.minecraft.server.NBTBase; import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagDouble; +import net.minecraft.server.NBTTagInt; import net.minecraft.server.NBTTagList; +import net.minecraft.server.NBTTagLong; import net.minecraft.server.NBTTagString; import org.apache.commons.lang.Validate; @@ -178,14 +181,28 @@ class CraftMetaItem implements ItemMeta, 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"); + @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"); private String displayName; private List lore; private Map enchantments; private int repairCost; + private final NBTTagList attributes; CraftMetaItem(CraftMetaItem meta) { if (meta == null) { + attributes = null; return; } @@ -200,6 +217,7 @@ class CraftMetaItem implements ItemMeta, Repairable { } this.repairCost = meta.repairCost; + this.attributes = meta.attributes; } CraftMetaItem(NBTTagCompound tag) { @@ -226,6 +244,51 @@ class CraftMetaItem implements ItemMeta, Repairable { if (tag.hasKey(REPAIR.NBT)) { repairCost = tag.getInt(REPAIR.NBT); } + + + if (tag.get(ATTRIBUTES.NBT) instanceof NBTTagList) { + NBTTagList save = null; + NBTTagList nbttaglist = tag.getList(ATTRIBUTES.NBT); + + for (int i = 0; i < nbttaglist.size(); ++i) { + if (!(nbttaglist.get(i) instanceof NBTTagCompound)) { + continue; + } + NBTTagCompound nbttagcompound = (NBTTagCompound) nbttaglist.get(i); + + if (!(nbttagcompound.get(ATTRIBUTES_UUID_HIGH.NBT) instanceof NBTTagLong)) { + continue; + } + if (!(nbttagcompound.get(ATTRIBUTES_UUID_LOW.NBT) instanceof NBTTagLong)) { + continue; + } + if (!(nbttagcompound.get(ATTRIBUTES_NAME.NBT) instanceof NBTTagString) || !CraftItemFactory.KNOWN_NBT_ATTRIBUTE_NAMES.contains(nbttagcompound.getString(ATTRIBUTES_NAME.NBT))) { + continue; + } + if (!(nbttagcompound.get(ATTRIBUTES_VALUE.NBT) instanceof NBTTagDouble)) { + continue; + } + if (!(nbttagcompound.get(ATTRIBUTES_TYPE.NBT) instanceof NBTTagInt) || nbttagcompound.getInt(ATTRIBUTES_TYPE.NBT) < 0 || nbttagcompound.getInt(ATTRIBUTES_TYPE.NBT) > 2) { + continue; + } + + if (save == null) { + save = new NBTTagList(ATTRIBUTES.NBT); + } + + 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_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); + } + + attributes = save; + } else { + attributes = null; + } } static Map buildEnchantments(NBTTagCompound tag, ItemMetaKey key) { @@ -260,6 +323,8 @@ class CraftMetaItem implements ItemMeta, Repairable { if (repairCost != null) { setRepairCost(repairCost); } + + attributes = null; } static Map buildEnchantments(Map map, ItemMetaKey key) { @@ -295,6 +360,10 @@ class CraftMetaItem implements ItemMeta, Repairable { if (hasRepairCost()) { itemTag.setInt(REPAIR.NBT, repairCost); } + + if (attributes != null) { + itemTag.set(ATTRIBUTES.NBT, attributes); + } } static NBTTagList createStringList(List list, ItemMetaKey key) { @@ -346,7 +415,7 @@ class CraftMetaItem implements ItemMeta, Repairable { @Overridden boolean isEmpty() { - return !(hasDisplayName() || hasEnchants() || hasLore()); + return !(hasDisplayName() || hasEnchants() || hasLore() || hasAttributes()); } public String getDisplayName() { @@ -365,6 +434,10 @@ class CraftMetaItem implements ItemMeta, Repairable { return this.lore != null && !this.lore.isEmpty(); } + public boolean hasAttributes() { + return this.attributes != null; + } + public boolean hasRepairCost() { return repairCost > 0; } @@ -458,6 +531,7 @@ class CraftMetaItem implements ItemMeta, Repairable { return ((this.hasDisplayName() ? that.hasDisplayName() && this.displayName.equals(that.displayName) : !that.hasDisplayName())) && (this.hasEnchants() ? that.hasEnchants() && this.enchantments.equals(that.enchantments) : !that.hasEnchants()) && (this.hasLore() ? that.hasLore() && this.lore.equals(that.lore) : !that.hasLore()) + && (this.hasAttributes() ? that.hasAttributes() && this.attributes.equals(that.attributes) : !that.hasAttributes()) && (this.hasRepairCost() ? that.hasRepairCost() && this.repairCost == that.repairCost : !that.hasRepairCost()); } @@ -482,6 +556,7 @@ class CraftMetaItem implements ItemMeta, Repairable { hash = 61 * hash + (hasDisplayName() ? this.displayName.hashCode() : 0); hash = 61 * hash + (hasLore() ? this.lore.hashCode() : 0); hash = 61 * hash + (hasEnchants() ? this.enchantments.hashCode() : 0); + hash = 61 * hash + (hasAttributes() ? this.attributes.hashCode() : 0); hash = 61 * hash + (hasRepairCost() ? this.repairCost : 0); return hash; } diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java new file mode 100644 index 0000000000..6e3690e23b --- /dev/null +++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemFactoryTest.java @@ -0,0 +1,45 @@ +package org.bukkit.craftbukkit.inventory; + +import static org.junit.Assert.*; +import static org.hamcrest.Matchers.*; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.zip.ZipEntry; +import java.util.zip.ZipFile; + +import net.minecraft.server.CommandAbstract; +import net.minecraft.server.IAttribute; +import org.bukkit.support.AbstractTestingBase; +import org.junit.Test; + +public class ItemFactoryTest extends AbstractTestingBase { + + @Test + public void testKnownAttributes() throws Throwable { + final ZipFile nmsZipFile = new ZipFile(CommandAbstract.class /* Magic class that isn't imported! */.getProtectionDomain().getCodeSource().getLocation().getFile()); + final Collection names = new HashSet(); + for (final ZipEntry clazzEntry : Collections.list(nmsZipFile.entries())) { + final String entryName = clazzEntry.getName(); + if (!(entryName.endsWith(".class") && entryName.startsWith("net/minecraft/server/"))) { + continue; + } + + final Class clazz = Class.forName(entryName.substring(0, entryName.length() - ".class".length()).replace('/', '.')); + assertThat(entryName, clazz, is(not(nullValue()))); + for (final Field field : clazz.getDeclaredFields()) { + if (IAttribute.class.isAssignableFrom(field.getType()) && Modifier.isStatic(field.getModifiers())) { + field.setAccessible(true); + final String attributeName = ((IAttribute) field.get(null)).a(); + assertThat("Logical error: duplicate name `" + attributeName + "' in " + clazz.getName(), names.add(attributeName), is(true)); + assertThat(clazz.getName(), CraftItemFactory.KNOWN_NBT_ATTRIBUTE_NAMES, hasItem(attributeName)); + } + } + } + + assertThat("Extra values detected", CraftItemFactory.KNOWN_NBT_ATTRIBUTE_NAMES, is(names)); + } +}