--- a/net/minecraft/server/TileEntityHopper.java
+++ b/net/minecraft/server/TileEntityHopper.java
@@ -4,12 +4,46 @@
 import java.util.List;
 import javax.annotation.Nullable;
 
+// CraftBukkit start
+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
+import org.bukkit.craftbukkit.inventory.CraftItemStack;
+import org.bukkit.entity.HumanEntity;
+import org.bukkit.event.inventory.InventoryMoveItemEvent;
+import org.bukkit.event.inventory.InventoryPickupItemEvent;
+import org.bukkit.inventory.Inventory;
+// CraftBukkit end
+
 public class TileEntityHopper extends TileEntityLootable implements IHopper, ITickable {
 
     private NonNullList<ItemStack> items;
     private int f;
     private long g;
 
+    // CraftBukkit start - add fields and methods
+    public List<HumanEntity> transaction = new java.util.ArrayList<HumanEntity>();
+    private int maxStack = MAX_STACK;
+
+    public List<ItemStack> getContents() {
+        return this.items;
+    }
+
+    public void onOpen(CraftHumanEntity who) {
+        transaction.add(who);
+    }
+
+    public void onClose(CraftHumanEntity who) {
+        transaction.remove(who);
+    }
+
+    public List<HumanEntity> getViewers() {
+        return transaction;
+    }
+
+    public void setMaxStackSize(int size) {
+        maxStack = size;
+    }
+    // CraftBukkit end
+
     public TileEntityHopper() {
         this.items = NonNullList.a(5, ItemStack.a);
         this.f = -1;
@@ -72,7 +106,7 @@
     }
 
     public int getMaxStackSize() {
-        return 64;
+        return maxStack; // CraftBukkit
     }
 
     public void e() {
@@ -163,10 +197,35 @@
                 for (int i = 0; i < this.getSize(); ++i) {
                     if (!this.getItem(i).isEmpty()) {
                         ItemStack itemstack = this.getItem(i).cloneItemStack();
-                        ItemStack itemstack1 = addItem(this, iinventory, this.splitStack(i, 1), enumdirection);
+                        // ItemStack itemstack1 = addItem(this, iinventory, this.splitStack(i, 1), enumdirection);
+
+                        // CraftBukkit start - Call event when pushing items into other inventories
+                        CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.splitStack(i, 1));
+
+                        Inventory destinationInventory;
+                        // Have to special case large chests as they work oddly
+                        if (iinventory instanceof InventoryLargeChest) {
+                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
+                        } else {
+                            destinationInventory = iinventory.getOwner().getInventory();
+                        }
+
+                        InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner().getInventory(), oitemstack.clone(), destinationInventory, true);
+                        this.getWorld().getServer().getPluginManager().callEvent(event);
+                        if (event.isCancelled()) {
+                            this.setItem(i, itemstack);
+                            this.setCooldown(8); // Delay hopper checks
+                            return false;
+                        }
+                        ItemStack itemstack1 = addItem(this, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection);
 
                         if (itemstack1.isEmpty()) {
-                            iinventory.update();
+                            if (event.getItem().equals(oitemstack)) {
+                                iinventory.update();
+                            } else {
+                                this.setItem(i, itemstack);
+                            }
+                            // CraftBukkit end
                             return true;
                         }
 
@@ -288,10 +347,41 @@
 
         if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) {
             ItemStack itemstack1 = itemstack.cloneItemStack();
-            ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null);
+            // ItemStack itemstack2 = addItem(iinventory, ihopper, iinventory.splitStack(i, 1), (EnumDirection) null);
+            // CraftBukkit start - Call event on collection of items from inventories into the hopper
+            CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.splitStack(i, 1));
+
+            Inventory sourceInventory;
+            // Have to special case large chests as they work oddly
+            if (iinventory instanceof InventoryLargeChest) {
+                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((InventoryLargeChest) iinventory);
+            } else {
+                sourceInventory = iinventory.getOwner().getInventory();
+            }
+
+            InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack.clone(), ihopper.getOwner().getInventory(), false);
+
+            ihopper.getWorld().getServer().getPluginManager().callEvent(event);
+            if (event.isCancelled()) {
+                iinventory.setItem(i, itemstack1);
+
+                if (ihopper instanceof TileEntityHopper) {
+                    ((TileEntityHopper) ihopper).setCooldown(8); // Delay hopper checks
+                } else if (ihopper instanceof EntityMinecartHopper) {
+                    ((EntityMinecartHopper) ihopper).setCooldown(4); // Delay hopper minecart checks
+                }
+
+                return false;
+            }
+            ItemStack itemstack2 = addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
 
             if (itemstack2.isEmpty()) {
-                iinventory.update();
+                if (event.getItem().equals(oitemstack)) {
+                    iinventory.update();
+                } else {
+                    iinventory.setItem(i, itemstack1);
+                }
+                // CraftBukkit end
                 return true;
             }
 
@@ -307,6 +397,13 @@
         if (entityitem == null) {
             return false;
         } else {
+            // CraftBukkit start
+            InventoryPickupItemEvent event = new InventoryPickupItemEvent(iinventory1.getOwner().getInventory(), (org.bukkit.entity.Item) entityitem.getBukkitEntity());
+            entityitem.world.getServer().getPluginManager().callEvent(event);
+            if (event.isCancelled()) {
+                return false;
+            }
+            // CraftBukkit end
             ItemStack itemstack = entityitem.getItemStack().cloneItemStack();
             ItemStack itemstack1 = addItem(iinventory, iinventory1, itemstack, (EnumDirection) null);