From 1fa9a81514073447453e0c1cde9c83fafd116d8a Mon Sep 17 00:00:00 2001
From: md_5 <git@md-5.net>
Date: Wed, 1 May 2019 20:18:01 +1000
Subject: [PATCH] SPIGOT-4802: Add CrossbowMeta

---
 .../inventory/CraftItemFactory.java           |   2 +
 .../craftbukkit/inventory/CraftItemStack.java |   2 +
 .../inventory/CraftMetaCrossbow.java          | 202 ++++++++++++++++++
 .../craftbukkit/inventory/CraftMetaItem.java  |   5 +-
 .../craftbukkit/inventory/ItemMetaTest.java   |   9 +
 5 files changed, 219 insertions(+), 1 deletion(-)
 create mode 100644 src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java

diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
index a01368f697..a4320f8446 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemFactory.java
@@ -252,6 +252,8 @@ public final class CraftItemFactory implements ItemFactory {
           return new CraftMetaBlockState(meta, material);
         case TROPICAL_FISH_BUCKET:
             return meta instanceof CraftMetaTropicalFishBucket ? meta : new CraftMetaTropicalFishBucket(meta);
+        case CROSSBOW:
+            return meta instanceof CraftMetaCrossbow ? meta : new CraftMetaCrossbow(meta);
         default:
             return new CraftMetaItem(meta);
         }
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
index 73dd802205..42f7bb0f7d 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftItemStack.java
@@ -503,6 +503,8 @@ public final class CraftItemStack extends ItemStack {
                 return new CraftMetaBlockState(item.getTag(), CraftMagicNumbers.getMaterial(item.getItem()));
             case TROPICAL_FISH_BUCKET:
                 return new CraftMetaTropicalFishBucket(item.getTag());
+            case CROSSBOW:
+                return new CraftMetaCrossbow(item.getTag());
             default:
                 return new CraftMetaItem(item.getTag());
         }
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java
new file mode 100644
index 0000000000..b3ace0bf2d
--- /dev/null
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaCrossbow.java
@@ -0,0 +1,202 @@
+package org.bukkit.craftbukkit.inventory;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableMap;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import net.minecraft.server.ItemArrow;
+import net.minecraft.server.NBTTagCompound;
+import net.minecraft.server.NBTTagList;
+import org.bukkit.Material;
+import org.bukkit.configuration.serialization.DelegateDeserialization;
+import org.bukkit.craftbukkit.util.CraftMagicNumbers;
+import org.bukkit.inventory.ItemStack;
+import org.bukkit.inventory.meta.CrossbowMeta;
+
+@DelegateDeserialization(CraftMetaItem.SerializableMeta.class)
+public class CraftMetaCrossbow extends CraftMetaItem implements CrossbowMeta {
+
+    static final ItemMetaKey CHARGED = new ItemMetaKey("Charged", "charged");
+    static final ItemMetaKey CHARGED_PROJECTILES = new ItemMetaKey("ChargedProjectiles", "charged-projectiles");
+    //
+    private boolean charged;
+    private List<ItemStack> chargedProjectiles;
+
+    CraftMetaCrossbow(CraftMetaItem meta) {
+        super(meta);
+
+        if (!(meta instanceof CraftMetaCrossbow)) {
+            return;
+        }
+
+        CraftMetaCrossbow crossbow = (CraftMetaCrossbow) meta;
+        this.charged = crossbow.charged;
+
+        if (crossbow.hasChargedProjectiles()) {
+            this.chargedProjectiles = new ArrayList<>(crossbow.chargedProjectiles);
+        }
+    }
+
+    CraftMetaCrossbow(NBTTagCompound tag) {
+        super(tag);
+
+        charged = tag.getBoolean(CHARGED.NBT);
+
+        if (tag.hasKeyOfType(CHARGED_PROJECTILES.NBT, CraftMagicNumbers.NBT.TAG_LIST)) {
+            NBTTagList list = tag.getList(CHARGED_PROJECTILES.NBT, CraftMagicNumbers.NBT.TAG_COMPOUND);
+
+            if (list != null && !list.isEmpty()) {
+                chargedProjectiles = new ArrayList<>();
+
+                for (int i = 0; i < list.size(); i++) {
+                    NBTTagCompound nbttagcompound1 = list.getCompound(i);
+
+                    chargedProjectiles.add(CraftItemStack.asCraftMirror(net.minecraft.server.ItemStack.a(nbttagcompound1)));
+                }
+            }
+        }
+    }
+
+    CraftMetaCrossbow(Map<String, Object> map) {
+        super(map);
+
+        Boolean charged = SerializableMeta.getObject(Boolean.class, map, CHARGED.BUKKIT, true);
+        if (charged != null) {
+            this.charged = charged;
+        }
+
+        Iterable<?> projectiles = SerializableMeta.getObject(Iterable.class, map, CHARGED_PROJECTILES.BUKKIT, true);
+        if (projectiles != null) {
+            for (Object stack : projectiles) {
+                if (stack instanceof ItemStack) {
+                    addChargedProjectile((ItemStack) stack);
+                }
+            }
+        }
+    }
+
+    @Override
+    void applyToItem(NBTTagCompound tag) {
+        super.applyToItem(tag);
+
+        tag.setBoolean(CHARGED.NBT, charged);
+        if (hasChargedProjectiles()) {
+            NBTTagList list = new NBTTagList();
+
+            for (ItemStack item : chargedProjectiles) {
+                NBTTagCompound saved = new NBTTagCompound();
+                CraftItemStack.asNMSCopy(item).save(saved);
+                list.add(saved);
+            }
+
+            tag.set(CHARGED_PROJECTILES.NBT, list);
+        }
+    }
+
+    @Override
+    boolean applicableTo(Material type) {
+        switch (type) {
+            case CROSSBOW:
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    @Override
+    boolean isEmpty() {
+        return super.isEmpty() && isCrossbowEmpty();
+    }
+
+    boolean isCrossbowEmpty() {
+        return !(hasChargedProjectiles());
+    }
+
+    @Override
+    public boolean hasChargedProjectiles() {
+        return chargedProjectiles != null;
+    }
+
+    @Override
+    public List<ItemStack> getChargedProjectiles() {
+        return (chargedProjectiles == null) ? null : ImmutableList.copyOf(chargedProjectiles);
+    }
+
+    @Override
+    public void setChargedProjectiles(List<ItemStack> projectiles) {
+        chargedProjectiles = null;
+        charged = false;
+
+        if (projectiles == null) {
+            return;
+        }
+
+        for (ItemStack i : projectiles) {
+            addChargedProjectile(i);
+        }
+    }
+
+    @Override
+    public void addChargedProjectile(ItemStack item) {
+        Preconditions.checkArgument(item != null, "item");
+        Preconditions.checkArgument(CraftMagicNumbers.getItem(item.getType()) instanceof ItemArrow, "Item %s is not an arrow", item);
+
+        if (chargedProjectiles == null) {
+            chargedProjectiles = new ArrayList<>();
+        }
+
+        charged = true;
+        chargedProjectiles.add(item);
+    }
+
+    @Override
+    boolean equalsCommon(CraftMetaItem meta) {
+        if (!super.equalsCommon(meta)) {
+            return false;
+        }
+        if (meta instanceof CraftMetaCrossbow) {
+            CraftMetaCrossbow that = (CraftMetaCrossbow) meta;
+
+            return this.charged == that.charged
+                    && (hasChargedProjectiles() ? that.hasChargedProjectiles() && this.chargedProjectiles.equals(that.chargedProjectiles) : !that.hasChargedProjectiles());
+        }
+        return true;
+    }
+
+    @Override
+    boolean notUncommon(CraftMetaItem meta) {
+        return super.notUncommon(meta) && (meta instanceof CraftMetaCrossbow || isCrossbowEmpty());
+    }
+
+    @Override
+    int applyHash() {
+        final int original;
+        int hash = original = super.applyHash();
+
+        if (hasChargedProjectiles()) {
+            hash = 61 * hash + (this.charged ? 1 : 0);
+            hash = 61 * hash + chargedProjectiles.hashCode();
+        }
+
+        return original != hash ? CraftMetaCrossbow.class.hashCode() ^ hash : hash;
+    }
+
+    @Override
+    public CraftMetaCrossbow clone() {
+        return (CraftMetaCrossbow) super.clone();
+    }
+
+    @Override
+    ImmutableMap.Builder<String, Object> serialize(ImmutableMap.Builder<String, Object> builder) {
+        super.serialize(builder);
+
+        builder.put(CHARGED.BUKKIT, charged);
+        if (hasChargedProjectiles()) {
+            builder.put(CHARGED_PROJECTILES.BUKKIT, chargedProjectiles);
+        }
+
+        return builder;
+    }
+}
diff --git a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
index 5b8c694f9a..e09ecd58a1 100644
--- a/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
+++ b/src/main/java/org/bukkit/craftbukkit/inventory/CraftMetaItem.java
@@ -153,6 +153,7 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
                     .put(CraftMetaCharge.class, "FIREWORK_EFFECT")
                     .put(CraftMetaKnowledgeBook.class, "KNOWLEDGE_BOOK")
                     .put(CraftMetaTropicalFishBucket.class, "TROPICAL_FISH_BUCKET")
+                    .put(CraftMetaCrossbow.class, "CROSSBOW")
                     .put(CraftMetaItem.class, "UNSPECIFIC")
                     .build();
 
@@ -1403,7 +1404,9 @@ class CraftMetaItem implements ItemMeta, Damageable, Repairable, BlockDataMeta {
                         CraftMetaCharge.EXPLOSION.NBT,
                         CraftMetaBlockState.BLOCK_ENTITY_TAG.NBT,
                         CraftMetaKnowledgeBook.BOOK_RECIPES.NBT,
-                        CraftMetaTropicalFishBucket.VARIANT.NBT
+                        CraftMetaTropicalFishBucket.VARIANT.NBT,
+                        CraftMetaCrossbow.CHARGED.NBT,
+                        CraftMetaCrossbow.CHARGED_PROJECTILES.NBT
                 ));
             }
             return HANDLED_TAGS;
diff --git a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java
index 176b8f5284..6abde9030d 100644
--- a/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java
+++ b/src/test/java/org/bukkit/craftbukkit/inventory/ItemMetaTest.java
@@ -35,6 +35,7 @@ import org.bukkit.inventory.meta.BannerMeta;
 import org.bukkit.inventory.meta.BlockDataMeta;
 import org.bukkit.inventory.meta.BlockStateMeta;
 import org.bukkit.inventory.meta.BookMeta;
+import org.bukkit.inventory.meta.CrossbowMeta;
 import org.bukkit.inventory.meta.EnchantmentStorageMeta;
 import org.bukkit.inventory.meta.FireworkEffectMeta;
 import org.bukkit.inventory.meta.FireworkMeta;
@@ -335,6 +336,14 @@ public class ItemMetaTest extends AbstractTestingBase {
                     cleanStack.setItemMeta(meta);
                     return cleanStack;
                 }
+            },
+            new StackProvider(Material.CROSSBOW) {
+                @Override ItemStack operate(ItemStack cleanStack) {
+                    final CrossbowMeta meta = (CrossbowMeta) cleanStack.getItemMeta();
+                    meta.addChargedProjectile(new ItemStack(Material.ARROW));
+                    cleanStack.setItemMeta(meta);
+                    return cleanStack;
+                }
             }
         );