From 1d740763173606e29afb0151ad95399e21aeb2ac Mon Sep 17 00:00:00 2001
From: Nathan Poirier <nathan@poirier.io>
Date: Sun, 22 Nov 2015 14:20:34 +0100
Subject: [PATCH] Add Villager Trade API


diff --git a/src/main/java/net/minecraft/server/EntityVillager.java b/src/main/java/net/minecraft/server/EntityVillager.java
index f1c905a..806e3b1 100644
--- a/src/main/java/net/minecraft/server/EntityVillager.java
+++ b/src/main/java/net/minecraft/server/EntityVillager.java
@@ -23,6 +23,7 @@ public class EntityVillager extends EntityAgeable implements IMerchant, NPC {
     private boolean bA;
     public InventorySubcontainer inventory;
     private static final EntityVillager.IMerchantRecipeOption[][][][] bC = new EntityVillager.IMerchantRecipeOption[][][][] { { { { new EntityVillager.MerchantRecipeOptionBuy(Items.WHEAT, new EntityVillager.MerchantOptionRandomRange(18, 22)), new EntityVillager.MerchantRecipeOptionBuy(Items.POTATO, new EntityVillager.MerchantOptionRandomRange(15, 19)), new EntityVillager.MerchantRecipeOptionBuy(Items.CARROT, new EntityVillager.MerchantOptionRandomRange(15, 19)), new EntityVillager.MerchantRecipeOptionSell(Items.BREAD, new EntityVillager.MerchantOptionRandomRange(-4, -2))}, { new EntityVillager.MerchantRecipeOptionBuy(Item.getItemOf(Blocks.PUMPKIN), new EntityVillager.MerchantOptionRandomRange(8, 13)), new EntityVillager.MerchantRecipeOptionSell(Items.PUMPKIN_PIE, new EntityVillager.MerchantOptionRandomRange(-3, -2))}, { new EntityVillager.MerchantRecipeOptionBuy(Item.getItemOf(Blocks.MELON_BLOCK), new EntityVillager.MerchantOptionRandomRange(7, 12)), new EntityVillager.MerchantRecipeOptionSell(Items.APPLE, new EntityVillager.MerchantOptionRandomRange(-5, -7))}, { new EntityVillager.MerchantRecipeOptionSell(Items.COOKIE, new EntityVillager.MerchantOptionRandomRange(-6, -10)), new EntityVillager.MerchantRecipeOptionSell(Items.CAKE, new EntityVillager.MerchantOptionRandomRange(1, 1))}}, { { new EntityVillager.MerchantRecipeOptionBuy(Items.STRING, new EntityVillager.MerchantOptionRandomRange(15, 20)), new EntityVillager.MerchantRecipeOptionBuy(Items.COAL, new EntityVillager.MerchantOptionRandomRange(16, 24)), new EntityVillager.MerchantRecipeOptionProcess(Items.FISH, new EntityVillager.MerchantOptionRandomRange(6, 6), Items.COOKED_FISH, new EntityVillager.MerchantOptionRandomRange(6, 6))}, { new EntityVillager.MerchantRecipeOptionEnchant(Items.FISHING_ROD, new EntityVillager.MerchantOptionRandomRange(7, 8))}}, { { new EntityVillager.MerchantRecipeOptionBuy(Item.getItemOf(Blocks.WOOL), new EntityVillager.MerchantOptionRandomRange(16, 22)), new EntityVillager.MerchantRecipeOptionSell(Items.SHEARS, new EntityVillager.MerchantOptionRandomRange(3, 4))}, { new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 0), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 1), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 2), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 3), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 4), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 5), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 6), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 7), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 8), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 9), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 10), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 11), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 12), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 13), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 14), new EntityVillager.MerchantOptionRandomRange(1, 2)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Item.getItemOf(Blocks.WOOL), 1, 15), new EntityVillager.MerchantOptionRandomRange(1, 2))}}, { { new EntityVillager.MerchantRecipeOptionBuy(Items.STRING, new EntityVillager.MerchantOptionRandomRange(15, 20)), new EntityVillager.MerchantRecipeOptionSell(Items.ARROW, new EntityVillager.MerchantOptionRandomRange(-12, -8))}, { new EntityVillager.MerchantRecipeOptionSell(Items.BOW, new EntityVillager.MerchantOptionRandomRange(2, 3)), new EntityVillager.MerchantRecipeOptionProcess(Item.getItemOf(Blocks.GRAVEL), new EntityVillager.MerchantOptionRandomRange(10, 10), Items.FLINT, new EntityVillager.MerchantOptionRandomRange(6, 10))}}}, { { { new EntityVillager.MerchantRecipeOptionBuy(Items.PAPER, new EntityVillager.MerchantOptionRandomRange(24, 36)), new EntityVillager.MerchantRecipeOptionBook()}, { new EntityVillager.MerchantRecipeOptionBuy(Items.BOOK, new EntityVillager.MerchantOptionRandomRange(8, 10)), new EntityVillager.MerchantRecipeOptionSell(Items.COMPASS, new EntityVillager.MerchantOptionRandomRange(10, 12)), new EntityVillager.MerchantRecipeOptionSell(Item.getItemOf(Blocks.BOOKSHELF), new EntityVillager.MerchantOptionRandomRange(3, 4))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.WRITTEN_BOOK, new EntityVillager.MerchantOptionRandomRange(2, 2)), new EntityVillager.MerchantRecipeOptionSell(Items.CLOCK, new EntityVillager.MerchantOptionRandomRange(10, 12)), new EntityVillager.MerchantRecipeOptionSell(Item.getItemOf(Blocks.GLASS), new EntityVillager.MerchantOptionRandomRange(-5, -3))}, { new EntityVillager.MerchantRecipeOptionBook()}, { new EntityVillager.MerchantRecipeOptionBook()}, { new EntityVillager.MerchantRecipeOptionSell(Items.NAME_TAG, new EntityVillager.MerchantOptionRandomRange(20, 22))}}}, { { { new EntityVillager.MerchantRecipeOptionBuy(Items.ROTTEN_FLESH, new EntityVillager.MerchantOptionRandomRange(36, 40)), new EntityVillager.MerchantRecipeOptionBuy(Items.GOLD_INGOT, new EntityVillager.MerchantOptionRandomRange(8, 10))}, { new EntityVillager.MerchantRecipeOptionSell(Items.REDSTONE, new EntityVillager.MerchantOptionRandomRange(-4, -1)), new EntityVillager.MerchantRecipeOptionSell(new ItemStack(Items.DYE, 1, EnumColor.BLUE.getInvColorIndex()), new EntityVillager.MerchantOptionRandomRange(-2, -1))}, { new EntityVillager.MerchantRecipeOptionSell(Items.ENDER_EYE, new EntityVillager.MerchantOptionRandomRange(7, 11)), new EntityVillager.MerchantRecipeOptionSell(Item.getItemOf(Blocks.GLOWSTONE), new EntityVillager.MerchantOptionRandomRange(-3, -1))}, { new EntityVillager.MerchantRecipeOptionSell(Items.EXPERIENCE_BOTTLE, new EntityVillager.MerchantOptionRandomRange(3, 11))}}}, { { { new EntityVillager.MerchantRecipeOptionBuy(Items.COAL, new EntityVillager.MerchantOptionRandomRange(16, 24)), new EntityVillager.MerchantRecipeOptionSell(Items.IRON_HELMET, new EntityVillager.MerchantOptionRandomRange(4, 6))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.IRON_INGOT, new EntityVillager.MerchantOptionRandomRange(7, 9)), new EntityVillager.MerchantRecipeOptionSell(Items.IRON_CHESTPLATE, new EntityVillager.MerchantOptionRandomRange(10, 14))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.DIAMOND, new EntityVillager.MerchantOptionRandomRange(3, 4)), new EntityVillager.MerchantRecipeOptionEnchant(Items.DIAMOND_CHESTPLATE, new EntityVillager.MerchantOptionRandomRange(16, 19))}, { new EntityVillager.MerchantRecipeOptionSell(Items.CHAINMAIL_BOOTS, new EntityVillager.MerchantOptionRandomRange(5, 7)), new EntityVillager.MerchantRecipeOptionSell(Items.CHAINMAIL_LEGGINGS, new EntityVillager.MerchantOptionRandomRange(9, 11)), new EntityVillager.MerchantRecipeOptionSell(Items.CHAINMAIL_HELMET, new EntityVillager.MerchantOptionRandomRange(5, 7)), new EntityVillager.MerchantRecipeOptionSell(Items.CHAINMAIL_CHESTPLATE, new EntityVillager.MerchantOptionRandomRange(11, 15))}}, { { new EntityVillager.MerchantRecipeOptionBuy(Items.COAL, new EntityVillager.MerchantOptionRandomRange(16, 24)), new EntityVillager.MerchantRecipeOptionSell(Items.IRON_AXE, new EntityVillager.MerchantOptionRandomRange(6, 8))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.IRON_INGOT, new EntityVillager.MerchantOptionRandomRange(7, 9)), new EntityVillager.MerchantRecipeOptionEnchant(Items.IRON_SWORD, new EntityVillager.MerchantOptionRandomRange(9, 10))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.DIAMOND, new EntityVillager.MerchantOptionRandomRange(3, 4)), new EntityVillager.MerchantRecipeOptionEnchant(Items.DIAMOND_SWORD, new EntityVillager.MerchantOptionRandomRange(12, 15)), new EntityVillager.MerchantRecipeOptionEnchant(Items.DIAMOND_AXE, new EntityVillager.MerchantOptionRandomRange(9, 12))}}, { { new EntityVillager.MerchantRecipeOptionBuy(Items.COAL, new EntityVillager.MerchantOptionRandomRange(16, 24)), new EntityVillager.MerchantRecipeOptionEnchant(Items.IRON_SHOVEL, new EntityVillager.MerchantOptionRandomRange(5, 7))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.IRON_INGOT, new EntityVillager.MerchantOptionRandomRange(7, 9)), new EntityVillager.MerchantRecipeOptionEnchant(Items.IRON_PICKAXE, new EntityVillager.MerchantOptionRandomRange(9, 11))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.DIAMOND, new EntityVillager.MerchantOptionRandomRange(3, 4)), new EntityVillager.MerchantRecipeOptionEnchant(Items.DIAMOND_PICKAXE, new EntityVillager.MerchantOptionRandomRange(12, 15))}}}, { { { new EntityVillager.MerchantRecipeOptionBuy(Items.PORKCHOP, new EntityVillager.MerchantOptionRandomRange(14, 18)), new EntityVillager.MerchantRecipeOptionBuy(Items.CHICKEN, new EntityVillager.MerchantOptionRandomRange(14, 18))}, { new EntityVillager.MerchantRecipeOptionBuy(Items.COAL, new EntityVillager.MerchantOptionRandomRange(16, 24)), new EntityVillager.MerchantRecipeOptionSell(Items.COOKED_PORKCHOP, new EntityVillager.MerchantOptionRandomRange(-7, -5)), new EntityVillager.MerchantRecipeOptionSell(Items.COOKED_CHICKEN, new EntityVillager.MerchantOptionRandomRange(-8, -6))}}, { { new EntityVillager.MerchantRecipeOptionBuy(Items.LEATHER, new EntityVillager.MerchantOptionRandomRange(9, 12)), new EntityVillager.MerchantRecipeOptionSell(Items.LEATHER_LEGGINGS, new EntityVillager.MerchantOptionRandomRange(2, 4))}, { new EntityVillager.MerchantRecipeOptionEnchant(Items.LEATHER_CHESTPLATE, new EntityVillager.MerchantOptionRandomRange(7, 12))}, { new EntityVillager.MerchantRecipeOptionSell(Items.SADDLE, new EntityVillager.MerchantOptionRandomRange(8, 10))}}}};
+    public boolean allowMultipleTraders; // FunCraft
 
     public EntityVillager(World world) {
         this(world, 0);
@@ -132,8 +133,20 @@ public class EntityVillager extends EntityAgeable implements IMerchant, NPC {
 
         if (!flag && this.isAlive() && !this.co() && !this.isBaby()) {
             if (!this.world.isClientSide && (this.br == null || this.br.size() > 0)) {
+                // FunCraft start
+                if (allowMultipleTraders && this.tradingPlayer != null)
+                {
+                    EntityVillager ev = new EntityVillager(this.world);
+                    ev.br = br;
+                    ev.a_(entityhuman);
+                    entityhuman.openTrade(ev);
+                }
+                else
+                {
+                // FunCraft end
                 this.a_(entityhuman);
                 entityhuman.openTrade(this);
+                }
             }
 
             entityhuman.b(StatisticList.F);
@@ -290,6 +303,10 @@ public class EntityVillager extends EntityAgeable implements IMerchant, NPC {
     }
 
     public boolean co() {
+        // FunCraft start
+        if (allowMultipleTraders)
+            return false;
+        // FunCraft end
         return this.tradingPlayer != null;
     }
 
diff --git a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
index 6fe21cc..4a0065c 100644
--- a/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
+++ b/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java
@@ -1,15 +1,31 @@
 package org.bukkit.craftbukkit.entity;
 
+import com.google.common.base.Function;
+import com.google.common.collect.Lists;
+import net.minecraft.server.EntityPlayer;
 import net.minecraft.server.EntityVillager;
+import net.minecraft.server.MerchantRecipe;
+import net.minecraft.server.MerchantRecipeList;
 import org.apache.commons.lang.Validate;
 import org.bukkit.craftbukkit.CraftServer;
 import org.bukkit.craftbukkit.inventory.CraftInventory;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
 import org.bukkit.entity.EntityType;
 import org.bukkit.entity.Villager;
 import org.bukkit.inventory.Inventory;
 import org.bukkit.inventory.InventoryHolder;
+import org.bukkit.inventory.ItemStack;
+
+import java.lang.reflect.Field;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Iterator;
 
 public class CraftVillager extends CraftAgeable implements Villager, InventoryHolder {
+    // FunCraft start
+    private Collection<CraftTrade> tradesView;
+    // FunCraft end
+
     public CraftVillager(CraftServer server, EntityVillager entity) {
         super(server, entity);
     }
@@ -41,4 +57,279 @@ public class CraftVillager extends CraftAgeable implements Villager, InventoryHo
     public Inventory getInventory() {
         return new CraftInventory(getHandle().inventory);
     }
+
+    // FunCraft start
+    @Override
+    public Collection<CraftTrade> getTrades()
+    {
+        if (tradesView == null)
+        {
+            final MerchantRecipeList recipeList = getHandle().getOffers(null);
+            tradesView = Lists.transform(recipeList, new Function<MerchantRecipe, CraftTrade>()
+            {
+                @Override
+                public CraftTrade apply(MerchantRecipe merchantRecipe)
+                {
+                    return new CraftTrade(merchantRecipe); // TODO: Import MerchantRecipe and get CraftTrade from merchantRecipe? (like Entity.getBukkitEntity)
+                }
+            });
+        }
+        return tradesView;
+    }
+
+    @Override
+    public CraftTrade addTrade(ItemStack buyingOne, ItemStack buyingTwo, ItemStack selling)
+    {
+        CraftTrade trade = new CraftTrade(buyingOne, buyingTwo, selling);
+        getHandle().getOffers(null).add(trade.getHandle());
+        return trade;
+    }
+
+    @Override
+    public CraftTrade addTrade(int index, ItemStack buyingOne, ItemStack buyingTwo, ItemStack selling)
+    {
+        CraftTrade trade = new CraftTrade(buyingOne, buyingTwo, selling);
+        getHandle().getOffers(null).add(index, trade.getHandle());
+        return trade;
+    }
+
+    @Override
+    public boolean removeTrade(Trade trade)
+    {
+        return trade instanceof CraftTrade && getHandle().getOffers(null).remove(((CraftTrade) trade).getHandle());
+    }
+
+    @Override
+    public boolean isAllowMultipleTraders()
+    {
+        return getHandle().allowMultipleTraders;
+    }
+
+    @Override
+    public void setAllowMultipleTraders(boolean allowMultipleTraders)
+    {
+        getHandle().allowMultipleTraders = allowMultipleTraders;
+    }
+
+    public static class CraftTrade implements Trade
+    {
+
+        private static Field buyingItem1Field;
+        private static Field buyingItem2Field;
+        private static Field sellingItemField;
+        private static Field usesField;
+        private static Field maxUsesField;
+        private static Field rewardExpField;
+
+        static
+        {
+            // TODO: Change field access and don't use horrible reflection!
+            try
+            {
+                buyingItem1Field = MerchantRecipe.class.getDeclaredField("buyingItem1");
+                buyingItem1Field.setAccessible(true);
+                buyingItem2Field = MerchantRecipe.class.getDeclaredField("buyingItem2");
+                buyingItem2Field.setAccessible(true);
+                sellingItemField = MerchantRecipe.class.getDeclaredField("sellingItem");
+                sellingItemField.setAccessible(true);
+                usesField = MerchantRecipe.class.getDeclaredField("uses");
+                usesField.setAccessible(true);
+                maxUsesField = MerchantRecipe.class.getDeclaredField("maxUses");
+                maxUsesField.setAccessible(true);
+                rewardExpField = MerchantRecipe.class.getDeclaredField("rewardExp");
+                rewardExpField.setAccessible(true);
+            }
+            catch (NoSuchFieldException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        private MerchantRecipe handle;
+
+        public CraftTrade(ItemStack buyingOne, ItemStack selling)
+        {
+            this(buyingOne, null, selling);
+        }
+
+        public CraftTrade(ItemStack buyingOne, ItemStack buyingTwo, ItemStack selling)
+        {
+            this.handle = new MerchantRecipe(CraftItemStack.asNMSCopy(buyingOne),
+                                             CraftItemStack.asNMSCopy(buyingTwo),
+                                             CraftItemStack.asNMSCopy(selling));
+        }
+
+        public CraftTrade(MerchantRecipe handle)
+        {
+            this.handle = handle;
+        }
+
+        @Override
+        public ItemStack getBuyingOne()
+        {
+            return CraftItemStack.asCraftMirror(handle.getBuyItem1());
+        }
+
+        @Override
+        public void setBuyingOne(ItemStack itemStack)
+        {
+            // handle.buyingItem1 = itemStack // TODO: public field access
+            try
+            {
+                buyingItem1Field.set(handle, itemStack);
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public ItemStack getBuyingTwo()
+        {
+            return CraftItemStack.asCraftMirror(handle.getBuyItem2());
+        }
+
+        @Override
+        public void setBuyingTwo(ItemStack itemStack)
+        {
+            // handle.buyingItem2 = itemStack // TODO: public field access
+            try
+            {
+                buyingItem2Field.set(handle, itemStack);
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public boolean hasBuyingTwo()
+        {
+            return handle.hasSecondItem();
+        }
+
+        @Override
+        public ItemStack getSelling()
+        {
+            return CraftItemStack.asCraftMirror(handle.getBuyItem3());
+        }
+
+        @Override
+        public void setSelling(ItemStack itemStack)
+        {
+            // handle.sellingItem = itemStack // TODO: public field access
+            try
+            {
+                sellingItemField.set(handle, itemStack);
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public int getUses()
+        {
+            return handle.e();
+        }
+
+        @Override
+        public void setUses(int uses)
+        {
+            // handle.uses = uses // TODO: public field access
+            try
+            {
+                usesField.set(handle, uses);
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public int getMaxUses()
+        {
+            return handle.f();
+        }
+
+        @Override
+        public void setMaxUses(int maxUses)
+        {
+            // handle.maxUses = maxUses // TODO: public field access
+            try
+            {
+                maxUsesField.set(handle, maxUses);
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        @Override
+        public void use()
+        {
+            handle.g();
+        }
+
+        @Override
+        public void increaseMaxUses(int increase)
+        {
+            handle.a(increase);
+        }
+
+        @Override
+        public boolean isUsedUp()
+        {
+            return handle.h();
+        }
+
+        @Override
+        public boolean canRewardExp()
+        {
+            return handle.j();
+        }
+
+        @Override
+        public void setRewardExp(boolean rewardExp)
+        {
+            // handle.rewardExp = maxUses // TODO: public field access
+            try
+            {
+                rewardExpField.set(handle, rewardExp);
+            }
+            catch (IllegalAccessException e)
+            {
+                e.printStackTrace();
+            }
+        }
+
+        public MerchantRecipe getHandle()
+        {
+            return handle;
+        }
+
+        @Override
+        public boolean equals(Object o)
+        {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            CraftTrade that = (CraftTrade) o;
+
+            return !(handle != null ? !handle.equals(that.handle) : that.handle != null);
+        }
+
+        @Override
+        public int hashCode()
+        {
+            return handle != null ? handle.hashCode() : 0;
+        }
+
+    }
+    // FunCraft end
 }
-- 
2.6.4

