diff options
Diffstat (limited to 'src/main/java/org')
25 files changed, 1889 insertions, 180 deletions
diff --git a/src/main/java/org/bukkit/craftbukkit/CraftServer.java b/src/main/java/org/bukkit/craftbukkit/CraftServer.java index b0a2e697..6f957c53 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftServer.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftServer.java @@ -71,6 +71,7 @@ import org.bukkit.conversations.Conversable; import org.bukkit.craftbukkit.help.SimpleHelpMap; import org.bukkit.craftbukkit.inventory.CraftFurnaceRecipe; import org.bukkit.craftbukkit.inventory.CraftInventoryCustom; +import org.bukkit.craftbukkit.inventory.CraftItemFactory; import org.bukkit.craftbukkit.inventory.CraftRecipe; import org.bukkit.craftbukkit.inventory.CraftShapedRecipe; import org.bukkit.craftbukkit.inventory.CraftShapelessRecipe; @@ -161,6 +162,7 @@ public final class CraftServer implements Server { static { ConfigurationSerialization.registerClass(CraftOfflinePlayer.class); + CraftItemFactory.instance(); } public CraftServer(MinecraftServer console, ServerConfigurationManagerAbstract server) { @@ -1320,4 +1322,8 @@ public final class CraftServer implements Server { Collections.sort(completions, String.CASE_INSENSITIVE_ORDER); return completions; } + + public CraftItemFactory getItemFactory() { + return CraftItemFactory.instance(); + } } diff --git a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java index 346b1833..19d891d7 100644 --- a/src/main/java/org/bukkit/craftbukkit/CraftWorld.java +++ b/src/main/java/org/bukkit/craftbukkit/CraftWorld.java @@ -283,8 +283,7 @@ public class CraftWorld implements World { public org.bukkit.entity.Item dropItem(Location loc, ItemStack item) { Validate.notNull(item, "Cannot drop a Null item."); Validate.isTrue(item.getTypeId() != 0, "Cannot drop AIR."); - CraftItemStack clone = new CraftItemStack(item); - EntityItem entity = new EntityItem(world, loc.getX(), loc.getY(), loc.getZ(), clone.getHandle()); + EntityItem entity = new EntityItem(world, loc.getX(), loc.getY(), loc.getZ(), CraftItemStack.asNMSCopy(item)); entity.pickupDelay = 10; world.addEntity(entity); // TODO this is inconsistent with how Entity.getBukkitEntity() works. diff --git a/src/main/java/org/bukkit/craftbukkit/Overridden.java b/src/main/java/org/bukkit/craftbukkit/Overridden.java new file mode 100644 index 00000000..1c19c69f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/Overridden.java @@ -0,0 +1,14 @@ +package org.bukkit.craftbukkit; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Indicates a method needs to be overridden in sub classes + */ +@Target({ElementType.CONSTRUCTOR, ElementType.METHOD}) +@Retention(RetentionPolicy.RUNTIME) +public @interface Overridden { +} diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java index 7dcdc7d2..c4bd959a 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftHumanEntity.java @@ -74,11 +74,11 @@ public class CraftHumanEntity extends CraftLivingEntity implements HumanEntity { } public ItemStack getItemOnCursor() { - return new CraftItemStack(getHandle().inventory.getCarried()); + return CraftItemStack.asCraftMirror(getHandle().inventory.getCarried()); } public void setItemOnCursor(ItemStack item) { - net.minecraft.server.ItemStack stack = CraftItemStack.createNMSItemStack(item); + net.minecraft.server.ItemStack stack = CraftItemStack.asNMSCopy(item); getHandle().inventory.setCarried(stack); if (this instanceof CraftPlayer) { ((EntityPlayer) getHandle()).broadcastCarriedItem(); // Send set slot for cursor diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java index 049d52ee..6bdfe3e7 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItem.java @@ -22,11 +22,11 @@ public class CraftItem extends CraftEntity implements Item { } public ItemStack getItemStack() { - return new CraftItemStack(item.itemStack); + return CraftItemStack.asCraftMirror(item.itemStack); } public void setItemStack(ItemStack stack) { - item.itemStack = CraftItemStack.createNMSItemStack(stack); + item.itemStack = CraftItemStack.asNMSCopy(stack); } public int getPickupDelay() { diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java index 9183e612..ef95acd6 100644 --- a/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java +++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftItemFrame.java @@ -20,12 +20,12 @@ public class CraftItemFrame extends CraftHanging implements ItemFrame { getHandle().getDataWatcher().a(2, 5); getHandle().getDataWatcher().h(2); } else { - getHandle().a(CraftItemStack.createNMSItemStack(item)); + getHandle().a(CraftItemStack.asNMSCopy(item)); } } public org.bukkit.inventory.ItemStack getItem() { - return CraftItemStack.asBukkitStack(getHandle().i()); + return CraftItemStack.asBukkitCopy(getHandle().i()); } public Rotation getRotation() { diff --git a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java index e225cbf4..106db16e 100644 --- a/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java +++ b/src/main/java/org/bukkit/craftbukkit/event/CraftEventFactory.java @@ -115,7 +115,7 @@ public class CraftEventFactory { private static PlayerEvent getPlayerBucketEvent(boolean isFilling, EntityHuman who, int clickedX, int clickedY, int clickedZ, int clickedFace, ItemStack itemstack, net.minecraft.server.Item item) { Player player = (who == null) ? null : (Player) who.getBukkitEntity(); - CraftItemStack itemInHand = new CraftItemStack(new ItemStack(item)); + CraftItemStack itemInHand = CraftItemStack.asNewCraftStack(item); Material bucket = Material.getMaterial(itemstack.id); CraftWorld craftWorld = (CraftWorld) player.getWorld(); @@ -150,7 +150,7 @@ public class CraftEventFactory { public static PlayerInteractEvent callPlayerInteractEvent(EntityHuman who, Action action, int clickedX, int clickedY, int clickedZ, int clickedFace, ItemStack itemstack) { Player player = (who == null) ? null : (Player) who.getBukkitEntity(); - CraftItemStack itemInHand = new CraftItemStack(itemstack); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); CraftWorld craftWorld = (CraftWorld) player.getWorld(); CraftServer craftServer = (CraftServer) player.getServer(); @@ -185,7 +185,7 @@ public class CraftEventFactory { */ public static EntityShootBowEvent callEntityShootBowEvent(EntityLiving who, ItemStack itemstack, EntityArrow entityArrow, float force) { LivingEntity shooter = (LivingEntity) who.getBukkitEntity(); - CraftItemStack itemInHand = new CraftItemStack(itemstack); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); Arrow arrow = (Arrow) entityArrow.getBukkitEntity(); if (itemInHand != null && (itemInHand.getType() == Material.AIR || itemInHand.getAmount() == 0)) { @@ -203,7 +203,7 @@ public class CraftEventFactory { */ public static BlockDamageEvent callBlockDamageEvent(EntityHuman who, int x, int y, int z, ItemStack itemstack, boolean instaBreak) { Player player = (who == null) ? null : (Player) who.getBukkitEntity(); - CraftItemStack itemInHand = new CraftItemStack(itemstack); + CraftItemStack itemInHand = CraftItemStack.asCraftMirror(itemstack); CraftWorld craftWorld = (CraftWorld) player.getWorld(); CraftServer craftServer = (CraftServer) player.getServer(); @@ -317,13 +317,7 @@ public class CraftEventFactory { for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { if (stack == null || stack.getType() == Material.AIR) continue; - if (stack instanceof CraftItemStack) { - // Use the internal item to preserve possible data. - victim.a(((CraftItemStack) stack).getHandle(), 0.0f); - } - else { - world.dropItemNaturally(entity.getLocation(), stack); - } + world.dropItemNaturally(entity.getLocation(), stack); } return event; @@ -344,12 +338,7 @@ public class CraftEventFactory { for (org.bukkit.inventory.ItemStack stack : event.getDrops()) { if (stack == null || stack.getType() == Material.AIR) continue; - if (stack instanceof CraftItemStack) { - // Use the internal item to preserve possible data. - victim.a(((CraftItemStack) stack).getHandle(), 0.0f); - } else { - world.dropItemNaturally(entity.getLocation(), stack); - } + world.dropItemNaturally(entity.getLocation(), stack); } return event; @@ -519,14 +508,14 @@ public class CraftEventFactory { public static ItemStack callPreCraftEvent(InventoryCrafting matrix, ItemStack result, InventoryView lastCraftView, boolean isRepair) { CraftInventoryCrafting inventory = new CraftInventoryCrafting(matrix, matrix.resultInventory); - inventory.setResult(new CraftItemStack(result)); + inventory.setResult(CraftItemStack.asCraftMirror(result)); PrepareItemCraftEvent event = new PrepareItemCraftEvent(inventory, lastCraftView, isRepair); Bukkit.getPluginManager().callEvent(event); org.bukkit.inventory.ItemStack bitem = event.getInventory().getResult(); - return CraftItemStack.createNMSItemStack(bitem); + return CraftItemStack.asNMSCopy(bitem); } public static ProjectileLaunchEvent callProjectileLaunchEvent(Entity entity) { @@ -556,7 +545,7 @@ public class CraftEventFactory { } public static void callPlayerItemBreakEvent(EntityHuman human, ItemStack brokenItem) { - CraftItemStack item = new CraftItemStack(brokenItem); + CraftItemStack item = CraftItemStack.asCraftMirror(brokenItem); PlayerItemBreakEvent event = new PlayerItemBreakEvent((Player) human.getBukkitEntity(), item); Bukkit.getPluginManager().callEvent(event); } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java index c402cf8c..2aca0eae 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftEntityEquipment.java @@ -75,11 +75,11 @@ public class CraftEntityEquipment implements EntityEquipment { } private ItemStack getEquipment(int slot) { - return CraftItemStack.asBukkitStack(entity.getHandle().getEquipment(slot)); + return CraftItemStack.asBukkitCopy(entity.getHandle().getEquipment(slot)); } private void setEquipment(int slot, ItemStack stack) { - entity.getHandle().setEquipment(slot, CraftItemStack.createNMSItemStack(stack)); + entity.getHandle().setEquipment(slot, CraftItemStack.asNMSCopy(stack)); } public void clear() { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java index 0df39891..34efe17e 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftFurnaceRecipe.java @@ -20,6 +20,6 @@ public class CraftFurnaceRecipe extends FurnaceRecipe implements CraftRecipe { public void addToCraftingManager() { ItemStack result = this.getResult(); ItemStack input = this.getInput(); - RecipesFurnace.getInstance().registerRecipe(input.getTypeId(), CraftItemStack.createNMSItemStack(result), 0.1f); + RecipesFurnace.getInstance().registerRecipe(input.getTypeId(), CraftItemStack.asNMSCopy(result), 0.1f); } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java index d60b1d82..537a479a 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventory.java @@ -44,7 +44,7 @@ public class CraftInventory implements Inventory { public ItemStack getItem(int index) { net.minecraft.server.ItemStack item = getInventory().getItem(index); - return item == null ? null : new CraftItemStack(item); + return item == null ? null : CraftItemStack.asCraftMirror(item); } public ItemStack[] getContents() { @@ -52,7 +52,7 @@ public class CraftInventory implements Inventory { net.minecraft.server.ItemStack[] mcItems = getInventory().getContents(); for (int i = 0; i < mcItems.length; i++) { - items[i] = mcItems[i] == null ? null : new CraftItemStack(mcItems[i]); + items[i] = mcItems[i] == null ? null : CraftItemStack.asCraftMirror(mcItems[i]); } return items; @@ -69,13 +69,13 @@ public class CraftInventory implements Inventory { if (i >= items.length) { mcItems[i] = null; } else { - mcItems[i] = CraftItemStack.createNMSItemStack(items[i]); + mcItems[i] = CraftItemStack.asNMSCopy(items[i]); } } } public void setItem(int index, ItemStack item) { - getInventory().setItem(index, ((item == null || item.getTypeId() == 0) ? null : CraftItemStack.createNMSItemStack(item))); + getInventory().setItem(index, ((item == null || item.getTypeId() == 0) ? null : CraftItemStack.asNMSCopy(item))); } public boolean contains(int materialId) { @@ -229,7 +229,7 @@ public class CraftInventory implements Inventory { public int firstPartial(ItemStack item) { ItemStack[] inventory = getContents(); - ItemStack filteredItem = new CraftItemStack(item); + ItemStack filteredItem = CraftItemStack.asCraftCopy(item); if (item == null) { return -1; } @@ -269,8 +269,7 @@ public class CraftInventory implements Inventory { } else { // More than a single stack! if (item.getAmount() > getMaxItemStack()) { - CraftItemStack stack = new CraftItemStack(item.getTypeId(), getMaxItemStack(), item.getDurability()); - stack.addUnsafeEnchantments(item.getEnchantments()); + CraftItemStack stack = CraftItemStack.asCraftCopy(item); setItem(firstFree, stack); item.setAmount(item.getAmount() - getMaxItemStack()); } else { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java index 81bde2c7..7bbf1df5 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCrafting.java @@ -47,13 +47,13 @@ public class CraftInventoryCrafting extends CraftInventory implements CraftingIn int i = 0; for (i = 0; i < mcResultItems.length; i++ ) { - items[i] = new CraftItemStack(mcResultItems[i]); + items[i] = CraftItemStack.asCraftMirror(mcResultItems[i]); } net.minecraft.server.ItemStack[] mcItems = getMatrixInventory().getContents(); for (int j = 0; j < mcItems.length; j++) { - items[i + j] = new CraftItemStack(mcItems[j]); + items[i + j] = CraftItemStack.asCraftMirror(mcItems[j]); } return items; @@ -68,19 +68,19 @@ public class CraftInventoryCrafting extends CraftInventory implements CraftingIn public CraftItemStack getItem(int index) { if (index < getResultInventory().getSize()) { net.minecraft.server.ItemStack item = getResultInventory().getItem(index); - return item == null ? null : new CraftItemStack(item); + return item == null ? null : CraftItemStack.asCraftMirror(item); } else { net.minecraft.server.ItemStack item = getMatrixInventory().getItem(index - getResultInventory().getSize()); - return item == null ? null : new CraftItemStack(item); + return item == null ? null : CraftItemStack.asCraftMirror(item); } } @Override public void setItem(int index, ItemStack item) { if (index < getResultInventory().getSize()) { - getResultInventory().setItem(index, (item == null ? null : CraftItemStack.createNMSItemStack(item))); + getResultInventory().setItem(index, (item == null ? null : CraftItemStack.asNMSCopy(item))); } else { - getMatrixInventory().setItem((index - getResultInventory().getSize()), (item == null ? null : CraftItemStack.createNMSItemStack(item))); + getMatrixInventory().setItem((index - getResultInventory().getSize()), (item == null ? null : CraftItemStack.asNMSCopy(item))); } } @@ -89,7 +89,7 @@ public class CraftInventoryCrafting extends CraftInventory implements CraftingIn net.minecraft.server.ItemStack[] matrix = getMatrixInventory().getContents(); for (int i = 0; i < matrix.length; i++ ) { - items[i] = new CraftItemStack(matrix[i]); + items[i] = CraftItemStack.asCraftMirror(matrix[i]); } return items; @@ -97,7 +97,7 @@ public class CraftInventoryCrafting extends CraftInventory implements CraftingIn public ItemStack getResult() { net.minecraft.server.ItemStack item = getResultInventory().getItem(0); - if(item != null) return new CraftItemStack(item); + if(item != null) return CraftItemStack.asCraftMirror(item); return null; } @@ -114,7 +114,7 @@ public class CraftInventoryCrafting extends CraftInventory implements CraftingIn if (item == null || item.getTypeId() <= 0) { mcItems[i] = null; } else { - mcItems[i] = CraftItemStack.createNMSItemStack(item); + mcItems[i] = CraftItemStack.asNMSCopy(item); } } else { mcItems[i] = null; @@ -127,7 +127,7 @@ public class CraftInventoryCrafting extends CraftInventory implements CraftingIn if (item == null || item.getTypeId() <= 0) { contents[0] = null; } else { - contents[0] = CraftItemStack.createNMSItemStack(item); + contents[0] = CraftItemStack.asNMSCopy(item); } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java index f54df7c4..6fb6cb8b 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryCustom.java @@ -66,7 +66,7 @@ public class CraftInventoryCustom extends CraftInventory { this.setItem(i, null); result = stack; } else { - result = new ItemStack(stack.id, j, stack.getData(), stack.getEnchantments()); + result = CraftItemStack.copyNMSStack(stack, j); stack.count -= j; } this.update(); @@ -81,7 +81,7 @@ public class CraftInventoryCustom extends CraftInventory { this.setItem(i, null); result = stack; } else { - result = new ItemStack(stack.id, 1, stack.getData(), stack.getEnchantments()); + result = CraftItemStack.copyNMSStack(stack, 1); stack.count -= 1; } return result; diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java index 9b7172d3..85bec9b7 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryPlayer.java @@ -22,7 +22,7 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i } public ItemStack getItemInHand() { - return new CraftItemStack(getInventory().getItemInHand()); + return CraftItemStack.asCraftMirror(getInventory().getItemInHand()); } public void setItemInHand(ItemStack stack) { @@ -70,7 +70,7 @@ public class CraftInventoryPlayer extends CraftInventory implements org.bukkit.i ItemStack[] ret = new ItemStack[mcItems.length]; for (int i = 0; i < mcItems.length; i++) { - ret[i] = new CraftItemStack(mcItems[i]); + ret[i] = CraftItemStack.asCraftMirror(mcItems[i]); } return ret; } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java index 0e930914..6ffc73de 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftInventoryView.java @@ -49,7 +49,7 @@ public class CraftInventoryView extends InventoryView { @Override public void setItem(int slot, ItemStack item) { - net.minecraft.server.ItemStack stack = CraftItemStack.createNMSItemStack(item); + net.minecraft.server.ItemStack stack = CraftItemStack.asNMSCopy(item); if (slot != -999) { container.getSlot(slot).set(stack); } else { @@ -62,7 +62,7 @@ public class CraftInventoryView extends InventoryView { if (slot == -999) { return null; } - return new CraftItemStack(container.getSlot(slot).getItem()); + return CraftItemStack.asCraftMirror(container.getSlot(slot).getItem()); } public boolean isInTop(int rawSlot) { diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java new file mode 100644 index 00000000..b2bcf344 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java @@ -0,0 +1,123 @@ +package org.bukkit.craftbukkit.inventory; + + +import org.apache.commons.lang.Validate; +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerialization; +import org.bukkit.inventory.ItemFactory; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.meta.ItemMeta; + +public final class CraftItemFactory implements ItemFactory { + static final Color DEFAULT_LEATHER_COLOR = Color.fromRGB(0xA06540); + private static final CraftItemFactory instance; + + static { + instance = new CraftItemFactory(); + ConfigurationSerialization.registerClass(CraftMetaItem.SerializableMeta.class); + } + + private CraftItemFactory() { + } + + public boolean isApplicable(ItemMeta meta, ItemStack itemstack) { + if (itemstack == null) { + return false; + } + return isApplicable(meta, itemstack.getType()); + } + + public boolean isApplicable(ItemMeta meta, Material type) { + if (type == null || meta == null) { + return false; + } + if (!(meta instanceof CraftMetaItem)) { + throw new IllegalArgumentException("Meta of " + meta.getClass().toString() + " not created by " + CraftItemFactory.class.getName()); + } + + return ((CraftMetaItem) meta).applicableTo(type); + } + + public ItemMeta getItemMeta(Material material) { + Validate.notNull(material, "Material cannot be null"); + return getItemMeta(material, null); + } + + private ItemMeta getItemMeta(Material material, CraftMetaItem meta) { + switch (material) { + case AIR: + return null; + case WRITTEN_BOOK: + case BOOK_AND_QUILL: + return meta instanceof CraftMetaBook ? meta : new CraftMetaBook(meta); + case SKULL_ITEM: + return meta instanceof CraftMetaSkull ? meta : new CraftMetaSkull(meta); + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + return meta instanceof CraftMetaLeatherArmor ? meta : new CraftMetaLeatherArmor(meta); + case POTION: + return meta instanceof CraftMetaPotion ? meta : new CraftMetaPotion(meta); + case MAP: + return meta instanceof CraftMetaMap ? meta : new CraftMetaMap(meta); + default: + return new CraftMetaItem(meta); + } + } + + public boolean equals(ItemMeta meta1, ItemMeta meta2) { + if (meta1 == meta2) { + return true; + } + if (meta1 != null && !(meta1 instanceof CraftMetaItem)) { + throw new IllegalArgumentException("First meta of " + meta1.getClass().getName() + " does not belong to " + CraftItemFactory.class.getName()); + } + if (meta2 != null && !(meta2 instanceof CraftMetaItem)) { + throw new IllegalArgumentException("Second meta " + meta2.getClass().getName() + " does not belong to " + CraftItemFactory.class.getName()); + } + if (meta1 == null) { + return ((CraftMetaItem) meta2).isEmpty(); + } + if (meta2 == null) { + return ((CraftMetaItem) meta1).isEmpty(); + } + + return equals((CraftMetaItem) meta1, (CraftMetaItem) meta2); + } + + boolean equals(CraftMetaItem meta1, CraftMetaItem meta2) { + /* + * This couldn't be done inside of the objects themselves, else force recursion. + * This is a fairly clean way of implementing it, by dividing the methods into purposes and letting each method perform its own function. + * + * The common and uncommon were split, as both could have variables not applicable to the other, like a skull and book. + * Each object needs its chance to say "hey wait a minute, we're not equal," but without the redundancy of using the 1.equals(2) && 2.equals(1) checking the 'commons' twice. + * + * Doing it this way fills all conditions of the .equals() method. + */ + return meta1.equalsCommon(meta2) && meta1.notUncommon(meta2) && meta2.notUncommon(meta1); + } + + public static CraftItemFactory instance() { + return instance; + } + + public ItemMeta asMetaFor(ItemMeta meta, ItemStack stack) { + Validate.notNull(stack, "Stack cannot be null"); + return asMetaFor(meta, stack.getType()); + } + + public ItemMeta asMetaFor(ItemMeta meta, Material material) { + Validate.notNull(material, "Material cannot be null"); + if (!(meta instanceof CraftMetaItem)) { + throw new IllegalArgumentException("Meta of " + (meta != null ? meta.getClass().toString() : "null") + " not created by " + CraftItemFactory.class.getName()); + } + return getItemMeta(material, (CraftMetaItem) meta); + } + + public Color getDefaultLeatherColor() { + return DEFAULT_LEATHER_COLOR; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java index 84b28a98..7de7c1d0 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java @@ -1,134 +1,150 @@ package org.bukkit.craftbukkit.inventory; -import java.util.HashMap; import java.util.Map; + import net.minecraft.server.EnchantmentManager; import net.minecraft.server.NBTTagCompound; import net.minecraft.server.NBTTagList; + +import org.apache.commons.lang.Validate; import org.bukkit.enchantments.Enchantment; import org.bukkit.inventory.ItemStack; import org.bukkit.Material; import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.inventory.meta.ItemMeta; + +import com.google.common.collect.ImmutableMap; @DelegateDeserialization(ItemStack.class) -public class CraftItemStack extends ItemStack { - protected net.minecraft.server.ItemStack item; +public final class CraftItemStack extends ItemStack { - public CraftItemStack(net.minecraft.server.ItemStack item) { - super( - item != null ? item.id: 0, - item != null ? item.count : 0, - (short)(item != null ? item.getData() : 0) - ); - this.item = item; + public static net.minecraft.server.ItemStack asNMSCopy(ItemStack original) { + if (original instanceof CraftItemStack) { + CraftItemStack stack = (CraftItemStack) original; + return stack.handle == null ? null : stack.handle.cloneItemStack(); + } + if (original == null || original.getTypeId() <= 0) { + return null; + } + net.minecraft.server.ItemStack stack = new net.minecraft.server.ItemStack(original.getTypeId(), original.getAmount(), original.getDurability()); + if (original.hasItemMeta()) { + setItemMeta(stack, original.getItemMeta()); + } + return stack; } - public CraftItemStack(ItemStack item) { - this(item.getTypeId(), item.getAmount(), item.getDurability()); - addUnsafeEnchantments(item.getEnchantments()); + public static net.minecraft.server.ItemStack copyNMSStack(net.minecraft.server.ItemStack original, int amount) { + net.minecraft.server.ItemStack stack = original.cloneItemStack(); + stack.count = amount; + return stack; } - /* 'Overwritten' constructors from ItemStack, yay for Java sucking */ - public CraftItemStack(final int type) { - this(type, 1); + /** + * Copies the NMS stack to return as a strictly-Bukkit stack + */ + public static ItemStack asBukkitCopy(net.minecraft.server.ItemStack original) { + if (original == null) { + return new ItemStack(Material.AIR); + } + ItemStack stack = new ItemStack(original.id, original.count, (short) original.getData()); + if (hasItemMeta(original)) { + stack.setItemMeta(getItemMeta(original)); + } + return stack; } - public CraftItemStack(final Material type) { - this(type, 1); + public static CraftItemStack asCraftMirror(net.minecraft.server.ItemStack original) { + return new CraftItemStack(original); } - public CraftItemStack(final int type, final int amount) { - this(type, amount, (byte) 0); + public static CraftItemStack asCraftCopy(ItemStack original) { + if (original instanceof CraftItemStack) { + CraftItemStack stack = (CraftItemStack) original; + return new CraftItemStack(stack.handle == null ? null : stack.handle.cloneItemStack()); + } + return new CraftItemStack(original); } - public CraftItemStack(final Material type, final int amount) { - this(type.getId(), amount); + public static CraftItemStack asNewCraftStack(net.minecraft.server.Item item) { + return asNewCraftStack(item, 1); } - public CraftItemStack(final int type, final int amount, final short damage) { - this(type, amount, damage, null); + public static CraftItemStack asNewCraftStack(net.minecraft.server.Item item, int amount) { + return new CraftItemStack(item.id, amount, (short) 0, null); } - public CraftItemStack(final Material type, final int amount, final short damage) { - this(type.getId(), amount, damage); - } + net.minecraft.server.ItemStack handle; - public CraftItemStack(final Material type, final int amount, final short damage, final Byte data) { - this(type.getId(), amount, damage, data); + /** + * Mirror + */ + private CraftItemStack(net.minecraft.server.ItemStack item) { + this.handle = item; } - public CraftItemStack(int type, int amount, short damage, Byte data) { - this(new net.minecraft.server.ItemStack(type, amount, data != null ? data : damage)); + private CraftItemStack(ItemStack item) { + this(item.getTypeId(), item.getAmount(), item.getDurability(), item.hasItemMeta() ? item.getItemMeta() : null); } - /* - * Unsure if we have to sync before each of these calls the values in 'item' - * are all public. - */ - - @Override - public Material getType() { - super.setTypeId(item != null ? item.id : 0); // sync, needed? - return super.getType(); + private CraftItemStack(int typeId, int amount, short durability, ItemMeta itemMeta) { + setTypeId(typeId); + setAmount(amount); + setDurability(durability); + setItemMeta(itemMeta); } @Override public int getTypeId() { - super.setTypeId(item != null ? item.id : 0); // sync, needed? - return item != null ? item.id : 0; + return handle != null ? handle.id : 0; } @Override public void setTypeId(int type) { - if (type == 0) { - super.setTypeId(0); - super.setAmount(0); - item = null; + if (getTypeId() == type) { + return; + } else if (type == 0) { + handle = null; + } else if (handle == null) { + handle = new net.minecraft.server.ItemStack(type, 1, 0); } else { - if (item == null) { - item = new net.minecraft.server.ItemStack(type, 1, 0); - super.setTypeId(type); - super.setAmount(1); - super.setDurability((short) 0); - } else { - item.id = type; - super.setTypeId(item.id); + handle.id = type; + if (hasItemMeta()) { + // This will create the appropriate item meta, which will contain all the data we intend to keep + setItemMeta(handle, getItemMeta(handle)); } } + setData(null); } @Override public int getAmount() { - super.setAmount(item != null ? item.count : 0); // sync, needed? - return (item != null ? item.count : 0); + return handle != null ? handle.count : 0; } @Override public void setAmount(int amount) { + if (handle == null) { + return; + } if (amount == 0) { - super.setTypeId(0); - super.setAmount(0); - item = null; + handle = null; } else { - super.setAmount(amount); - item.count = amount; + handle.count = amount; } } @Override public void setDurability(final short durability) { // Ignore damage if item is null - if (item != null) { - super.setDurability(durability); - item.setData(durability); + if (handle != null) { + handle.setData(durability); } } @Override public short getDurability() { - if (item != null) { - super.setDurability((short) item.getData()); // sync, needed? - return (short) item.getData(); + if (handle != null) { + return (short) handle.getData(); } else { return -1; } @@ -136,14 +152,46 @@ public class CraftItemStack extends ItemStack { @Override public int getMaxStackSize() { - return (item == null) ? 0 : item.getItem().getMaxStackSize(); + return (handle == null) ? Material.AIR.getMaxStackSize() : handle.getItem().getMaxStackSize(); } @Override public void addUnsafeEnchantment(Enchantment ench, int level) { - Map<Enchantment, Integer> enchantments = getEnchantments(); - enchantments.put(ench, level); - rebuildEnchantments(enchantments); + Validate.notNull(ench, "Cannot add null enchantment"); + + if (!makeTag(handle)) { + return; + } + NBTTagList list = getEnchantmentList(handle), listCopy; + if (list == null) { + list = new NBTTagList("ench"); + handle.tag.set("ench", list); + } + int size = list.size(); + + for (int i = 0; i < size; i++) { + NBTTagCompound tag = (NBTTagCompound) list.get(i); + short id = tag.getShort("id"); + if (id == ench.getId()) { + tag.setShort("lvl", (short) level); + return; + } + } + NBTTagCompound tag = new NBTTagCompound(); + tag.setShort("id", (short) ench.getId()); + tag.setShort("lvl", (short) level); + list.add(tag); + } + + static boolean makeTag(net.minecraft.server.ItemStack item) { + if (item == null) { + return false; + } + if (item.tag != null) { + return true; + } + item.tag = new NBTTagCompound(); + return true; } @Override @@ -153,31 +201,65 @@ public class CraftItemStack extends ItemStack { @Override public int getEnchantmentLevel(Enchantment ench) { - if (item == null) return 0; - return EnchantmentManager.getEnchantmentLevel(ench.getId(), item); + Validate.notNull(ench, "Cannot find null enchantment"); + if (handle == null) { + return 0; + } + return EnchantmentManager.getEnchantmentLevel(ench.getId(), handle); } @Override public int removeEnchantment(Enchantment ench) { - Map<Enchantment, Integer> enchantments = getEnchantments(); - Integer previous = enchantments.remove(ench); + Validate.notNull(ench, "Cannot remove null enchantment"); - rebuildEnchantments(enchantments); + NBTTagList list = getEnchantmentList(handle), listCopy; + if (list == null) { + return 0; + } + int index = Integer.MIN_VALUE, size = list.size(), level; - return (previous == null) ? 0 : previous; + for (int i = 0; i < size; i++) { + short id = ((NBTTagCompound) list.get(i)).getShort("id"); + if (id == ench.getId()) { + index = i; + break; + } + } + + if (index == Integer.MIN_VALUE) { + return 0; + } + if (index == 0 && size == 0) { + handle.tag.o("ench"); + if (handle.tag.d()) { + handle.tag = null; + } + } + + listCopy = new NBTTagList("ench"); + level = Integer.MAX_VALUE; + for (int i = 0; i < size; i++) { + if (i == index) { + level = ((NBTTagCompound) list.get(i)).getShort("id"); + continue; + } + listCopy.add(list.get(i)); + } + handle.tag.set("ench", listCopy); + return level; } @Override public Map<Enchantment, Integer> getEnchantments() { - return getEnchantments(item); + return getEnchantments(handle); } - public static Map<Enchantment, Integer> getEnchantments(net.minecraft.server.ItemStack item) { - Map<Enchantment, Integer> result = new HashMap<Enchantment, Integer>(); + static Map<Enchantment, Integer> getEnchantments(net.minecraft.server.ItemStack item) { + ImmutableMap.Builder<Enchantment, Integer> result = ImmutableMap.builder(); NBTTagList list = (item == null) ? null : item.getEnchantments(); if (list == null) { - return result; + return result.build(); } for (int i = 0; i < list.size(); i++) { @@ -187,66 +269,111 @@ public class CraftItemStack extends ItemStack { result.put(Enchantment.getById(id), (int) level); } - return result; + return result.build(); } - private void rebuildEnchantments(Map<Enchantment, Integer> enchantments) { - if (item == null) return; - - NBTTagCompound tag = item.tag; - NBTTagList list = new NBTTagList("ench"); + static NBTTagList getEnchantmentList(net.minecraft.server.ItemStack item) { + return item == null ? null : item.getEnchantments(); + } - if (tag == null) { - tag = item.tag = new NBTTagCompound(); + @Override + public CraftItemStack clone() { + CraftItemStack itemStack = (CraftItemStack) super.clone(); + if (this.handle != null) { + itemStack.handle = this.handle.cloneItemStack(); } + return itemStack; + } - for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) { - NBTTagCompound subtag = new NBTTagCompound(); - - subtag.setShort("id", (short) entry.getKey().getId()); - subtag.setShort("lvl", (short) (int) entry.getValue()); + @Override + public ItemMeta getItemMeta() { + return getItemMeta(handle); + } - list.add(subtag); + static ItemMeta getItemMeta(net.minecraft.server.ItemStack item) { + if (!hasItemMeta(item)) { + return CraftItemFactory.instance().getItemMeta(getType(item)); } - - if (enchantments.isEmpty()) { - tag.remove("ench"); - } else { - tag.set("ench", list); + switch (getType(item)) { + case WRITTEN_BOOK: + case BOOK_AND_QUILL: + return new CraftMetaBook(item.tag); + case SKULL_ITEM: + return new CraftMetaSkull(item.tag); + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + return new CraftMetaLeatherArmor(item.tag); + case POTION: + return new CraftMetaPotion(item.tag); + case MAP: + return new CraftMetaMap(item.tag); + default: + return new CraftMetaItem(item.tag); } } - public net.minecraft.server.ItemStack getHandle() { - return item; + static Material getType(net.minecraft.server.ItemStack item) { + Material material = Material.getMaterial(item == null ? 0 : item.id); + return material == null ? Material.AIR : material; } @Override - public CraftItemStack clone() { - CraftItemStack itemStack = (CraftItemStack) super.clone(); - if (this.item != null) { - itemStack.item = this.item.cloneItemStack(); - } - return itemStack; + public boolean setItemMeta(ItemMeta itemMeta) { + return setItemMeta(handle, itemMeta); } - public static net.minecraft.server.ItemStack createNMSItemStack(ItemStack original) { - if (original == null || original.getTypeId() <= 0) { - return null; - } else if (original instanceof CraftItemStack) { - return ((CraftItemStack) original).getHandle(); + static boolean setItemMeta(net.minecraft.server.ItemStack item, ItemMeta itemMeta) { + if (item == null) { + return false; + } + if (itemMeta == null) { + item.tag = null; + return true; } - return new CraftItemStack(original).getHandle(); + if (!CraftItemFactory.instance().isApplicable(itemMeta, getType(item))) { + return false; + } + + NBTTagCompound tag = new NBTTagCompound(); + item.setTag(tag); + + ((CraftMetaItem) itemMeta).applyToItem(tag); + return true; } - /** - * Copies the NMS stack to return as a strictly-Bukkit stack - */ - public static ItemStack asBukkitStack(net.minecraft.server.ItemStack original) { - if (original == null) { - return new ItemStack(Material.AIR); + @Override + public boolean isSimilar(ItemStack stack) { + if (stack == null) { + return false; } - ItemStack stack = new ItemStack(original.id, original.count, (short) original.getData()); - stack.addUnsafeEnchantments(getEnchantments(original)); - return stack; + if (stack == this) { + return true; + } + if (!(stack instanceof CraftItemStack)) { + return stack.getClass() == ItemStack.class && stack.isSimilar(this); + } + + CraftItemStack that = (CraftItemStack) stack; + if (handle == that.handle) { + return true; + } + if (handle == null || that.handle == null) { + return false; + } + if (!(that.getTypeId() == getTypeId() && getDurability() == that.getDurability())) { + return false; + } + return hasItemMeta() ? that.hasItemMeta() && handle.tag.equals(that.handle.tag) : !that.hasItemMeta(); + } + + @Override + public boolean hasItemMeta() { + return hasItemMeta(handle); + } + + static boolean hasItemMeta(net.minecraft.server.ItemStack item) { + return !(item == null || item.tag == null || item.tag.d()); } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java new file mode 100644 index 00000000..b49974b5 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaBook.java @@ -0,0 +1,272 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.List; +import java.util.Map; + +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagList; +import net.minecraft.server.NBTTagString; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.BookMeta; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaBook extends CraftMetaItem implements BookMeta { + static final ItemMetaKey BOOK_TITLE = new ItemMetaKey("title"); + static final ItemMetaKey BOOK_AUTHOR = new ItemMetaKey("author"); + static final ItemMetaKey BOOK_PAGES = new ItemMetaKey("pages"); + static final int MAX_PAGE_LENGTH = 256; + static final int MAX_TITLE_LENGTH = 0xffff; + + private String title; + private String author; + private List<String> pages = new ArrayList<String>(); + + CraftMetaBook(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaBook)) { + return; + } + CraftMetaBook bookMeta = (CraftMetaBook) meta; + this.title = bookMeta.title; + this.author = bookMeta.author; + pages.addAll(bookMeta.pages); + } + + CraftMetaBook(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(BOOK_TITLE.NBT)) { + this.title = tag.getString(BOOK_TITLE.NBT); + } + + if (tag.hasKey(BOOK_AUTHOR.NBT)) { + this.author = tag.getString(BOOK_AUTHOR.NBT); + } + + if (tag.hasKey(BOOK_PAGES.NBT)) { + NBTTagList pages = tag.getList(BOOK_PAGES.NBT); + String[] pageArray = new String[pages.size()]; + + for (int i = 0; i < pages.size(); i++) { + String page = ((NBTTagString) pages.get(i)).data; + pageArray[i] = page; + } + + addPage(pageArray); + } + } + + CraftMetaBook(Map<String, Object> map) { + super(map); + + setAuthor(SerializableMeta.getString(map, BOOK_AUTHOR.BUKKIT, true)); + + setTitle(SerializableMeta.getString(map, BOOK_TITLE.BUKKIT, true)); + + Collection<?> pages = SerializableMeta.getObject(Collection.class, map, BOOK_PAGES.BUKKIT, true); + CraftMetaItem.safelyAdd(pages, this.pages, MAX_PAGE_LENGTH); + } + + @Override + void applyToItem(NBTTagCompound itemData) { + super.applyToItem(itemData); + + if (hasTitle()) { + itemData.setString(BOOK_TITLE.NBT, this.title); + } + + if (hasAuthor()) { + itemData.setString(BOOK_AUTHOR.NBT, this.author); + } + + if (hasPages()) { + NBTTagList itemPages = new NBTTagList(BOOK_PAGES.NBT); + for (int i = 1; i < pages.size() + 1; i++) { + itemPages.add(new NBTTagString(String.valueOf(i), pages.get(i - 1))); + } + itemData.set(BOOK_PAGES.NBT, itemPages); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isBookEmpty(); + } + + boolean isBookEmpty() { + return !(hasPages() || hasAuthor() || hasTitle()); + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case WRITTEN_BOOK: + case BOOK_AND_QUILL: + return true; + default: + return false; + } + } + + public boolean hasAuthor() { + return !Strings.isNullOrEmpty(author); + } + + public boolean hasTitle() { + return !Strings.isNullOrEmpty(title); + } + + public boolean hasPages() { + return !pages.isEmpty(); + } + + public String getTitle() { + return this.title; + } + + public boolean setTitle(final String title) { + if (title == null) { + this.title = null; + return true; + } else if (title.length() > MAX_TITLE_LENGTH) { + return false; + } + + this.title = title; + return true; + } + + public String getAuthor() { + return this.author; + } + + public void setAuthor(final String author) { + this.author = author; + } + + public String getPage(final int page) { + Validate.isTrue(isValidPage(page), "Invalid page number"); + return pages.get(page - 1); + } + + public void setPage(final int page, final String text) { + if (!isValidPage(page)) { + throw new IllegalArgumentException("Invalid page number " + page + "/" + pages.size()); + } + + pages.set(page - 1, text == null ? "" : text.length() > MAX_PAGE_LENGTH ? text.substring(0, MAX_PAGE_LENGTH) : text); + } + + public void setPages(final String... pages) { + this.pages.clear(); + + addPage(pages); + } + + public void addPage(final String... pages) { + for (String page : pages) { + if (page == null) { + page = ""; + } else if (page.length() > MAX_PAGE_LENGTH) { + page = page.substring(0, MAX_PAGE_LENGTH); + } + + this.pages.add(page); + } + } + + public int getPageCount() { + return pages.size(); + } + + public List<String> getPages() { + return ImmutableList.copyOf(pages); + } + + public void setPages(List<String> pages) { + this.pages.clear(); + CraftMetaItem.safelyAdd(pages, this.pages, MAX_PAGE_LENGTH); + } + + private boolean isValidPage(int page) { + return page > 0 && page <= pages.size(); + } + + @Override + public CraftMetaBook clone() { + CraftMetaBook meta = (CraftMetaBook) super.clone(); + meta.pages = new ArrayList<String>(pages); + return meta; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasTitle()) { + hash = 61 * hash + this.title.hashCode(); + } + if (hasAuthor()) { + hash = 61 * hash + 13 * this.author.hashCode(); + } + if (hasPages()) { + hash = 61 * hash + 17 * this.pages.hashCode(); + } + return original != hash ? CraftMetaBook.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaBook) { + CraftMetaBook that = (CraftMetaBook) meta; + + return (hasTitle() ? that.hasTitle() && this.title.equals(that.title) : !that.hasTitle()) + && (hasAuthor() ? that.hasAuthor() && this.author.equals(that.author) : !that.hasAuthor()) + && (hasPages() ? that.hasPages() && this.pages.equals(that.pages) : !that.hasPages()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaBook || isBookEmpty()); + } + + @Override + Builder<String, Object> serialize(Builder<String, Object> builder) { + super.serialize(builder); + + if (hasTitle()) { + builder.put(BOOK_TITLE.BUKKIT, title); + } + + if (hasAuthor()) { + builder.put(BOOK_AUTHOR.BUKKIT, author); + } + + if (hasPages()) { + builder.put(BOOK_PAGES.BUKKIT, pages); + } + + return builder; + } + + @Override + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.BOOK; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java new file mode 100644 index 00000000..87709d73 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java @@ -0,0 +1,510 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +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.bukkit.Material; +import org.bukkit.configuration.serialization.ConfigurationSerializable; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.configuration.serialization.SerializableAs; +import org.bukkit.craftbukkit.Overridden; +import org.bukkit.enchantments.Enchantment; +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; + +/** + * Children must include the following: + * + * <li> Constructor(CraftMetaItem meta) + * <li> Constructor(NBTTagCompound tag) + * <li> Constructor(Map<String, Object> map) + * <br><br> + * <li> void applyToItem(NBTTagCompound tag) + * <li> boolean applicableTo(Material type) + * <br><br> + * <li> boolean notUncommon(CraftMetaItem meta) + * <li> boolean equalsCommon(CraftMetaItem meta) + * <br><br> + * <li> boolean isEmpty() + * <li> boolean is{Type}Empty() + * <br><br> + * <li> int applyHash() + * <li> public Class clone() + * <br><br> + * <li> Builder<String, Object> serialize(Builder<String, Object> builder) + * <li> SerializableMeta.Deserializers deserializer() + */ +@DelegateDeserialization(CraftMetaItem.SerializableMeta.class) +class CraftMetaItem implements ItemMeta, Repairable { + static class ItemMetaKey { + 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"; + + enum Deserializers { + BOOK { + @Override + CraftMetaBook deserialize(Map<String, Object> map) { + return new CraftMetaBook(map); + } + }, + SKULL { + @Override + CraftMetaSkull deserialize(Map<String, Object> map) { + return new CraftMetaSkull(map); + } + }, + LEATHER_ARMOR { + @Override + CraftMetaLeatherArmor deserialize(Map<String, Object> map) { + return new CraftMetaLeatherArmor(map); + } + }, + MAP { + @Override + CraftMetaMap deserialize(Map<String, Object> map) { + return new CraftMetaMap(map); + } + }, + POTION { + @Override + CraftMetaPotion deserialize(Map<String, Object> map) { + return new CraftMetaPotion(map); + } + }, + UNSPECIFIC { + @Override + CraftMetaItem deserialize(Map<String, Object> map) { + return new CraftMetaItem(map); + } + }; + + abstract CraftMetaItem deserialize(Map<String, Object> map); + } + + private SerializableMeta() { + } + + public static ItemMeta deserialize(Map<String, Object> map) { + Validate.notNull(map, "Cannot deserialize null map"); + + String type = getString(map, TYPE_FIELD, false); + Deserializers deserializer = Deserializers.valueOf(type); + + if (deserializer == null) { + throw new IllegalArgumentException(type + " is not a valid " + TYPE_FIELD); + } + + return deserializer.deserialize(map); + } + + public Map<String, Object> 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> T getObject(Class<T> 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 DISPLAY = new ItemMetaKey("display"); + static final ItemMetaKey LORE = new ItemMetaKey("Lore", "lore"); + static final ItemMetaKey ENCHANTMENTS = new ItemMetaKey("ench", "enchants"); + static final ItemMetaKey ENCHANTMENTS_ID = new ItemMetaKey("id"); + static final ItemMetaKey ENCHANTMENTS_LVL = new ItemMetaKey("lvl"); + static final ItemMetaKey REPAIR = new ItemMetaKey("RepairCost", "repair-cost"); + + private String displayName; + private List<String> lore; + private Map<Enchantment, Integer> enchantments; + private int repairCost; + + CraftMetaItem(CraftMetaItem meta) { + if (meta == null) { + return; + } + + this.displayName = meta.displayName; + + if (meta.hasLore()) { + this.lore = new ArrayList<String>(meta.lore); + } + + if (meta.hasEnchants()) { + this.enchantments = new HashMap<Enchantment, Integer>(meta.enchantments); + } + + this.repairCost = meta.repairCost; + } + + CraftMetaItem(NBTTagCompound tag) { + if (tag.hasKey(DISPLAY.NBT)) { + NBTTagCompound display = tag.getCompound(DISPLAY.NBT); + + if (display.hasKey(NAME.NBT)) { + displayName = display.getString(NAME.NBT); + } + + if (display.hasKey(LORE.NBT)) { + NBTTagList list = display.getList(LORE.NBT); + lore = new ArrayList<String>(list.size()); + + for (int index = 0; index < list.size(); index++) { + String line = ((NBTTagString) list.get(index)).data; + lore.add(line); + } + } + } + + if (tag.hasKey(ENCHANTMENTS.NBT)) { + NBTTagList ench = tag.getList(ENCHANTMENTS.NBT); + enchantments = new HashMap<Enchantment, Integer>(ench.size()); + + for (int i = 0; i < ench.size(); i++) { + short id = ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_ID.NBT); + short level = ((NBTTagCompound) ench.get(i)).getShort(ENCHANTMENTS_LVL.NBT); + + addEnchant(Enchantment.getById(id), (int) level, true); + } + } + + if (tag.hasKey(REPAIR.NBT)) { + repairCost = tag.getInt(REPAIR.NBT); + } + } + + CraftMetaItem(Map<String, Object> map) { + setDisplayName(SerializableMeta.getString(map, NAME.BUKKIT, true)); + + if (map.containsKey(LORE.BUKKIT)) { + lore = (List<String>) map.get(LORE.BUKKIT); + } + + Map<?, ?> ench = SerializableMeta.getObject(Map.class, map, ENCHANTMENTS.BUKKIT, true); + if (ench != null) { + enchantments = new HashMap<Enchantment, Integer>(ench.size()); + for (Map.Entry<?, ?> entry : ench.entrySet()) { + Enchantment enchantment = Enchantment.getByName(entry.getKey().toString()); + + if ((enchantment != null) && (entry.getValue() instanceof Integer)) { + addEnchant(enchantment, (Integer) entry.getValue(), true); + } + } + } + + if (map.containsKey(REPAIR.BUKKIT)) { + repairCost = (Integer) map.get(REPAIR.BUKKIT); + } + } + + @Overridden + void applyToItem(NBTTagCompound itemTag) { + if (hasDisplayName()) { + setDisplayTag(itemTag, NAME.NBT, new NBTTagString(NAME.NBT, displayName)); + } + + if (hasLore()) { + NBTTagList list = new NBTTagList(LORE.NBT); + for (int i = 0; i < lore.size(); i++) { + list.add(new NBTTagString(String.valueOf(i), lore.get(i))); + } + setDisplayTag(itemTag, LORE.NBT, list); + } + + if (hasEnchants()) { + NBTTagList list = new NBTTagList(ENCHANTMENTS.NBT); + + for (Map.Entry<Enchantment, Integer> entry : enchantments.entrySet()) { + NBTTagCompound subtag = new NBTTagCompound(); + + subtag.setShort(ENCHANTMENTS_ID.NBT, (short) entry.getKey().getId()); + subtag.setShort(ENCHANTMENTS_LVL.NBT, (short) (int) entry.getValue()); + + list.add(subtag); + } + + itemTag.set(ENCHANTMENTS.NBT, list); + } + + if (hasRepairCost()) { + itemTag.setInt(REPAIR.NBT, repairCost); + } + } + + void setDisplayTag(NBTTagCompound tag, String key, NBTBase value) { + final NBTTagCompound display = tag.getCompound(DISPLAY.NBT); + + if (!tag.hasKey(DISPLAY.NBT)) { + tag.setCompound(DISPLAY.NBT, display); + } + + display.set(key, value); + } + + @Overridden + boolean applicableTo(Material type) { + return type != Material.AIR; + } + + @Overridden + boolean isEmpty() { + return !(hasDisplayName() || hasEnchants() || hasLore()); + } + + public String getDisplayName() { + return displayName; + } + + public final void setDisplayName(String name) { + this.displayName = name; + } + + public boolean hasDisplayName() { + return !Strings.isNullOrEmpty(displayName); + } + + public boolean hasLore() { + return this.lore != null && !this.lore.isEmpty(); + } + + public boolean hasRepairCost() { + return repairCost > 0; + } + + public boolean hasEnchant(Enchantment ench) { + return hasEnchants() ? enchantments.containsKey(ench) : false; + } + + public int getEnchantLevel(Enchantment ench) { + Integer level = hasEnchants() ? enchantments.get(ench) : null; + if (level == null) { + return 0; + } + return level; + } + + public Map<Enchantment, Integer> getEnchants() { + return hasEnchants() ? ImmutableMap.copyOf(enchantments) : ImmutableMap.<Enchantment, Integer>of(); + } + + public boolean addEnchant(Enchantment ench, int level, boolean ignoreRestrictions) { + if (enchantments == null) { + enchantments = new HashMap<Enchantment, Integer>(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) { + return hasEnchants() ? enchantments.remove(ench) != null : false; + } + + public boolean hasEnchants() { + return !(enchantments == null || enchantments.isEmpty()); + } + + public List<String> getLore() { + return this.lore == null ? null : new ArrayList<String>(this.lore); + } + + public void setLore(List<String> 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<String>(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 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.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 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 + (hasLore() ? this.lore.hashCode() : 0); + hash = 61 * hash + (hasEnchants() ? this.enchantments.hashCode() : 0); + hash = 61 * hash + (hasRepairCost() ? this.repairCost : 0); + return hash; + } + + @Overridden + @Override + public CraftMetaItem clone() { + try { + return (CraftMetaItem) super.clone(); + } catch (CloneNotSupportedException e) { + throw new Error(e); + } + } + + public final Map<String, Object> serialize() { + ImmutableMap.Builder<String, Object> map = ImmutableMap.builder(); + map.put(SerializableMeta.TYPE_FIELD, deserializer().name()); + serialize(map); + return map.build(); + } + + @Overridden + ImmutableMap.Builder<String, Object> serialize(ImmutableMap.Builder<String, Object> builder) { + if (hasDisplayName()) { + builder.put(NAME.BUKKIT, displayName); + } + + if (hasLore()) { + builder.put(LORE.BUKKIT, ImmutableList.copyOf(lore)); + } + + if (hasEnchants()) { + ImmutableMap.Builder<String, Integer> enchantments = ImmutableMap.builder(); + for (Map.Entry<Enchantment, Integer> enchant : this.enchantments.entrySet()) { + enchantments.put(enchant.getKey().getName(), enchant.getValue()); + } + builder.put(ENCHANTMENTS.BUKKIT, enchantments.build()); + } + + if (hasRepairCost()) { + builder.put(REPAIR.BUKKIT, repairCost); + } + + return builder; + } + + @Overridden + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.UNSPECIFIC; + } + + static void safelyAdd(Collection<?> addFrom, Collection<String> 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); + } + } + } + + @Override + public final String toString() { + return serialize().toString(); // TODO: cry + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java new file mode 100644 index 00000000..6cb3bb1f --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaLeatherArmor.java @@ -0,0 +1,137 @@ +package org.bukkit.craftbukkit.inventory; + +import static org.bukkit.craftbukkit.inventory.CraftItemFactory.DEFAULT_LEATHER_COLOR; + +import java.util.Map; + +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagInt; + +import org.bukkit.Color; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.LeatherArmorMeta; + +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaLeatherArmor extends CraftMetaItem implements LeatherArmorMeta { + static final ItemMetaKey COLOR = new ItemMetaKey("color"); + + private Color color = DEFAULT_LEATHER_COLOR; + + CraftMetaLeatherArmor(CraftMetaItem meta) { + super(meta); + if (!(meta instanceof CraftMetaLeatherArmor)) { + return; + } + + CraftMetaLeatherArmor armorMeta = (CraftMetaLeatherArmor) meta; + this.color = armorMeta.color; + } + + CraftMetaLeatherArmor(NBTTagCompound tag) { + super(tag); + if (tag.hasKey(DISPLAY.NBT)) { + NBTTagCompound display = tag.getCompound(DISPLAY.NBT); + if (display.hasKey(COLOR.NBT)) { + color = Color.fromRGB(display.getInt(COLOR.NBT)); + } + } + } + + CraftMetaLeatherArmor(Map<String, Object> map) { + super(map); + setColor(SerializableMeta.getObject(Color.class, map, COLOR.BUKKIT, true)); + } + + void applyToItem(NBTTagCompound itemTag) { + super.applyToItem(itemTag); + + if (hasColor()) { + setDisplayTag(itemTag, COLOR.NBT, new NBTTagInt(COLOR.NBT, color.asRGB())); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isLeatherArmorEmpty(); + } + + boolean isLeatherArmorEmpty() { + return !(hasColor()); + } + + boolean applicableTo(Material type) { + switch(type) { + case LEATHER_HELMET: + case LEATHER_CHESTPLATE: + case LEATHER_LEGGINGS: + case LEATHER_BOOTS: + return true; + default: + return false; + } + } + + @Override + public CraftMetaLeatherArmor clone() { + return (CraftMetaLeatherArmor) super.clone(); + } + + public Color getColor() { + return color; + } + + public void setColor(Color color) { + this.color = color == null ? DEFAULT_LEATHER_COLOR : color; + } + + boolean hasColor() { + return !DEFAULT_LEATHER_COLOR.equals(color); + } + + @Override + Builder<String, Object> serialize(Builder<String, Object> builder) { + super.serialize(builder); + + if (hasColor()) { + builder.put(COLOR.BUKKIT, color); + } + + return builder; + } + + @Override + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.LEATHER_ARMOR; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaLeatherArmor) { + CraftMetaLeatherArmor that = (CraftMetaLeatherArmor) meta; + + return color.equals(that.color); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaLeatherArmor || isLeatherArmorEmpty()); + } + + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasColor()) { + hash ^= color.hashCode(); + } + return original != hash ? CraftMetaSkull.class.hashCode() ^ hash : hash; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java new file mode 100644 index 00000000..0bd57c03 --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaMap.java @@ -0,0 +1,139 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.server.NBTTagCompound; + +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.MapMeta; + +import com.google.common.collect.ImmutableMap; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaMap extends CraftMetaItem implements MapMeta { + static final ItemMetaKey MAP_SCALING = new ItemMetaKey("map_is_scaling", "scaling"); + static final byte SCALING_EMPTY = (byte) 0; + static final byte SCALING_TRUE = (byte) 1; + static final byte SCALING_FALSE = (byte) 2; + + private byte scaling = SCALING_EMPTY; + + CraftMetaMap(CraftMetaItem meta) { + super(meta); + + if (!(meta instanceof CraftMetaMap)) { + return; + } + + CraftMetaMap map = (CraftMetaMap) meta; + this.scaling = map.scaling; + } + + CraftMetaMap(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(MAP_SCALING.NBT)) { + this.scaling = tag.getBoolean(MAP_SCALING.NBT) ? SCALING_TRUE : SCALING_FALSE; + } + } + + CraftMetaMap(Map<String, Object> map) { + super(map); + + if (map.containsKey(MAP_SCALING.BUKKIT)) { + this.scaling = SerializableMeta.getBoolean(map, MAP_SCALING.BUKKIT) ? SCALING_TRUE : SCALING_FALSE; + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + if (hasScaling()) { + tag.setBoolean(MAP_SCALING.NBT, isScaling()); + } + } + + @Override + boolean applicableTo(Material type) { + switch (type) { + case MAP: + return true; + default: + return false; + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isMapEmpty(); + } + + boolean isMapEmpty() { + return !hasScaling(); + } + + boolean hasScaling() { + return scaling != SCALING_EMPTY; + } + + public boolean isScaling() { + return scaling == SCALING_TRUE; + } + + public void setScaling(boolean scaling) { + this.scaling = scaling ? SCALING_TRUE : SCALING_FALSE; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaMap) { + CraftMetaMap that = (CraftMetaMap) meta; + + return (this.scaling == that.scaling); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaMap || isMapEmpty()); + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + + if (hasScaling()) { + hash ^= 0x22222222 << (isScaling() ? 1 : -1); + } + + return original != hash ? CraftMetaMap.class.hashCode() ^ hash : hash; + } + + public CraftMetaMap clone() { + return (CraftMetaMap) super.clone(); + } + + @Override + ImmutableMap.Builder<String, Object> serialize(ImmutableMap.Builder<String, Object> builder) { + super.serialize(builder); + + if (hasScaling()) { + builder.put(MAP_SCALING.BUKKIT, scaling); + } + + return builder; + } + + @Override + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.MAP; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java new file mode 100644 index 00000000..fa21f40d --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaPotion.java @@ -0,0 +1,260 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + +import net.minecraft.server.NBTTagCompound; +import net.minecraft.server.NBTTagList; + +import org.apache.commons.lang.Validate; +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.inventory.meta.PotionMeta; +import org.bukkit.potion.PotionEffect; +import org.bukkit.potion.PotionEffectType; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; + +import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaPotion extends CraftMetaItem implements PotionMeta { + static final ItemMetaKey AMPLIFIER = new ItemMetaKey("Amplifier", "amplifier"); + static final ItemMetaKey AMBIENT = new ItemMetaKey("Ambient", "ambient"); + static final ItemMetaKey DURATION = new ItemMetaKey("Duration", "duration"); + static final ItemMetaKey POTION_EFFECTS = new ItemMetaKey("CustomPotionEffects", "custom-effects"); + static final ItemMetaKey ID = new ItemMetaKey("ID", "potion-id"); + + private List<PotionEffect> customEffects; + + CraftMetaPotion(CraftMetaItem meta) { + super(meta); + if (!(meta instanceof CraftMetaPotion)) { + return; + } + CraftMetaPotion potionMeta = (CraftMetaPotion) meta; + if (potionMeta.hasCustomEffects()) { + this.customEffects = new ArrayList<PotionEffect>(potionMeta.customEffects); + } + } + + CraftMetaPotion(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(POTION_EFFECTS.NBT)) { + NBTTagList list = tag.getList(POTION_EFFECTS.NBT); + int length = list.size(); + if (length > 0) { + customEffects = new ArrayList<PotionEffect>(length); + + for (int i = 0; i < length; i++) { + NBTTagCompound effect = (NBTTagCompound) list.get(i); + PotionEffectType type = PotionEffectType.getById(effect.getByte(ID.NBT)); + int amp = effect.getByte(AMPLIFIER.NBT); + int duration = effect.getInt(DURATION.NBT); + boolean ambient = effect.getBoolean(AMBIENT.NBT); + customEffects.add(new PotionEffect(type, duration, amp, ambient)); + } + } + } + } + + CraftMetaPotion(Map<String, Object> map) { + super(map); + + List<?> rawEffectList = SerializableMeta.getObject(List.class, map, POTION_EFFECTS.BUKKIT, true); + if (rawEffectList == null) { + return; + } + + for (Object obj : rawEffectList) { + if (!(obj instanceof PotionEffect)) { + throw new IllegalArgumentException("Object in effect list is not valid. " + obj.getClass()); + } + addCustomEffect((PotionEffect) obj, true); + } + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + if (hasCustomEffects()) { + NBTTagList effectList = new NBTTagList(); + tag.set(POTION_EFFECTS.NBT, effectList); + + for (PotionEffect effect : customEffects) { + NBTTagCompound effectData = new NBTTagCompound(); + effectData.setByte(ID.NBT, (byte) effect.getType().getId()); + effectData.setByte(AMPLIFIER.NBT, (byte) effect.getAmplifier()); + effectData.setInt(DURATION.NBT, effect.getDuration()); + effectData.setBoolean(AMBIENT.NBT, effect.isAmbient()); + effectList.add(effectData); + } + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isPotionEmpty(); + } + + boolean isPotionEmpty() { + return !(hasCustomEffects()); + } + + @Override + boolean applicableTo(Material type) { + switch(type) { + case POTION: + return true; + default: + return false; + } + } + + @Override + public CraftMetaPotion clone() { + CraftMetaPotion clone = (CraftMetaPotion) super.clone(); + if (hasCustomEffects()) { + clone.customEffects = new ArrayList<PotionEffect>(customEffects); + } + return clone; + } + + public boolean hasCustomEffects() { + return !(customEffects == null || customEffects.isEmpty()); + } + + public List<PotionEffect> getCustomEffects() { + if (hasCustomEffects()) { + return ImmutableList.copyOf(customEffects); + } + return ImmutableList.of(); + } + + public boolean addCustomEffect(PotionEffect effect, boolean overwrite) { + Validate.notNull(effect, "Potion effect must not be null"); + + int index = indexOfEffect(effect.getType()); + if (index != -1) { + if (overwrite) { + PotionEffect old = customEffects.get(index); + if (old.getAmplifier() == effect.getAmplifier() && old.getDuration() == effect.getDuration() && old.isAmbient() == effect.isAmbient()) { + return false; + } + customEffects.set(index, effect); + return true; + } else { + return false; + } + } else { + if (customEffects == null) { + customEffects = new ArrayList<PotionEffect>(); + } + customEffects.add(effect); + return true; + } + } + + public boolean removeCustomEffect(PotionEffectType type) { + Validate.notNull(type, "Potion effect type must not be null"); + + if (!hasCustomEffects()) { + return false; + } + + boolean changed = false; + Iterator<PotionEffect> iterator = customEffects.iterator(); + while (iterator.hasNext()) { + PotionEffect effect = iterator.next(); + if (effect.getType() == type) { + iterator.remove(); + changed = true; + } + } + return changed; + } + + public boolean hasCustomEffect(PotionEffectType type) { + Validate.notNull(type, "Potion effect type must not be null"); + return indexOfEffect(type) != -1; + } + + public boolean setMainEffect(PotionEffectType type) { + Validate.notNull(type, "Potion effect type must not be null"); + int index = indexOfEffect(type); + if (index == -1 || index == 0) { + return false; + } + + PotionEffect old = customEffects.get(0); + customEffects.set(0, customEffects.get(index)); + customEffects.set(index, old); + return true; + } + + private int indexOfEffect(PotionEffectType type) { + if (!hasCustomEffects()) { + return -1; + } + + for (int i = 0; i < customEffects.size(); i++) { + if (customEffects.get(i).getType().equals(type)) { + return i; + } + } + return -1; + } + + public boolean clearCustomEffects() { + boolean changed = hasCustomEffects(); + customEffects = null; + return changed; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasCustomEffects()) { + hash = 73 * hash + customEffects.hashCode(); + } + return original != hash ? CraftMetaPotion.class.hashCode() ^ hash : hash; + } + + @Override + public boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaPotion) { + CraftMetaPotion that = (CraftMetaPotion) meta; + + return (this.hasCustomEffects() ? that.hasCustomEffects() && this.customEffects.equals(that.customEffects) : !that.hasCustomEffects()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaPotion || isPotionEmpty()); + } + + @Override + Builder<String, Object> serialize(Builder<String, Object> builder) { + super.serialize(builder); + + if (hasCustomEffects()) { + builder.put(POTION_EFFECTS.BUKKIT, ImmutableList.copyOf(this.customEffects)); + } + + return builder; + } + + @Override + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.POTION; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java new file mode 100644 index 00000000..2bef968e --- /dev/null +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaSkull.java @@ -0,0 +1,134 @@ +package org.bukkit.craftbukkit.inventory; + +import java.util.Map; + +import net.minecraft.server.NBTTagCompound; + +import org.bukkit.Material; +import org.bukkit.configuration.serialization.DelegateDeserialization; +import org.bukkit.craftbukkit.inventory.CraftMetaItem.SerializableMeta; +import org.bukkit.inventory.meta.SkullMeta; + +import com.google.common.base.Strings; +import com.google.common.collect.ImmutableMap.Builder; + +@DelegateDeserialization(SerializableMeta.class) +class CraftMetaSkull extends CraftMetaItem implements SkullMeta { + static final ItemMetaKey SKULL_OWNER = new ItemMetaKey("SkullOwner", "skull-owner"); + static final int MAX_OWNER_LENGTH = 16; + + private String player; + + CraftMetaSkull(CraftMetaItem meta) { + super(meta); + if (!(meta instanceof CraftMetaSkull)) { + return; + } + CraftMetaSkull skullMeta = (CraftMetaSkull) meta; + this.player = skullMeta.player; + } + + CraftMetaSkull(NBTTagCompound tag) { + super(tag); + + if (tag.hasKey(SKULL_OWNER.NBT)) { + player = tag.getString(SKULL_OWNER.NBT); + } + } + + CraftMetaSkull(Map<String, Object> map) { + super(map); + setOwner(SerializableMeta.getString(map, SKULL_OWNER.BUKKIT, true)); + } + + @Override + void applyToItem(NBTTagCompound tag) { + super.applyToItem(tag); + + if (hasOwner()) { + tag.setString(SKULL_OWNER.NBT, player); + } + } + + @Override + boolean isEmpty() { + return super.isEmpty() && isSkullEmpty(); + } + + boolean isSkullEmpty() { + return !(hasOwner()); + } + + @Override + boolean applicableTo(Material type) { + switch(type) { + case SKULL_ITEM: + return true; + default: + return false; + } + } + + @Override + public CraftMetaSkull clone() { + return (CraftMetaSkull) super.clone(); + } + + public boolean hasOwner() { + return !Strings.isNullOrEmpty(player); + } + + public String getOwner() { + return player; + } + + public boolean setOwner(String name) { + if (name != null && name.length() > MAX_OWNER_LENGTH) { + return false; + } + player = name; + return true; + } + + @Override + int applyHash() { + final int original; + int hash = original = super.applyHash(); + if (hasOwner()) { + hash = 61 * hash + player.hashCode(); + } + return original != hash ? CraftMetaSkull.class.hashCode() ^ hash : hash; + } + + @Override + boolean equalsCommon(CraftMetaItem meta) { + if (!super.equalsCommon(meta)) { + return false; + } + if (meta instanceof CraftMetaSkull) { + CraftMetaSkull that = (CraftMetaSkull) meta; + + return (this.hasOwner() ? that.hasOwner() && this.player.equals(that.player) : !that.hasOwner()); + } + return true; + } + + @Override + boolean notUncommon(CraftMetaItem meta) { + return super.notUncommon(meta) && (meta instanceof CraftMetaSkull || isSkullEmpty()); + } + + @Override + Builder<String, Object> serialize(Builder<String, Object> builder) { + super.serialize(builder); + if (hasOwner()) { + return builder.put(SKULL_OWNER.BUKKIT, this.player); + } + return builder; + } + + @Override + SerializableMeta.Deserializers deserializer() { + return SerializableMeta.Deserializers.SKULL; + } +} diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java index 5d89d79d..0f416a88 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapedRecipe.java @@ -59,6 +59,6 @@ public class CraftShapedRecipe extends ShapedRecipe implements CraftRecipe { data[i] = new net.minecraft.server.ItemStack(id, 1, dmg); i++; } - CraftingManager.getInstance().registerShapedRecipe(CraftItemStack.createNMSItemStack(this.getResult()), data); + CraftingManager.getInstance().registerShapedRecipe(CraftItemStack.asNMSCopy(this.getResult()), data); } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java index 919df561..b06aa548 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftShapelessRecipe.java @@ -42,6 +42,6 @@ public class CraftShapelessRecipe extends ShapelessRecipe implements CraftRecipe data[i] = new net.minecraft.server.ItemStack(id, 1, dmg); i++; } - CraftingManager.getInstance().registerShapelessRecipe(CraftItemStack.createNMSItemStack(this.getResult()), data); + CraftingManager.getInstance().registerShapelessRecipe(CraftItemStack.asNMSCopy(this.getResult()), data); } } diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java index c1f21936..f8e864e4 100644 --- a/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java +++ b/src/main/java/org/bukkit/craftbukkit/inventory/RecipeIterator.java @@ -34,7 +34,7 @@ public class RecipeIterator implements Iterator<Recipe> { } else { removeFrom = smelting; int id = smelting.next(); - CraftItemStack stack = new CraftItemStack(RecipesFurnace.getInstance().getResult(id)); + CraftItemStack stack = CraftItemStack.asCraftMirror(RecipesFurnace.getInstance().getResult(id)); CraftFurnaceRecipe recipe = new CraftFurnaceRecipe(stack, new ItemStack(id, 1, (short) -1)); return recipe; } |