--- a/net/minecraft/server/TileEntityHopper.java
+++ b/net/minecraft/server/TileEntityHopper.java
@@ -7,12 +7,51 @@
 import java.util.stream.IntStream;
 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 j;
     private long k;
 
+    // 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;
+    }
+
+    @Override
+    public int getMaxStackSize() {
+        return maxStack;
+    }
+
+    public void setMaxStackSize(int size) {
+        maxStack = size;
+    }
+    // CraftBukkit end
+
     public TileEntityHopper() {
         super(TileEntityTypes.HOPPER);
         this.items = NonNullList.a(5, ItemStack.b);
@@ -138,7 +177,28 @@
                 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);
+                        // CraftBukkit end
 
                         if (itemstack1.isEmpty()) {
                             iinventory.update();
@@ -203,7 +263,34 @@
 
         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);
+            // CraftBukkit end
 
             if (itemstack2.isEmpty()) {
                 iinventory.update();
@@ -218,6 +305,13 @@
 
     public static boolean a(IInventory iinventory, EntityItem entityitem) {
         boolean flag = false;
+        // CraftBukkit start
+        InventoryPickupItemEvent event = new InventoryPickupItemEvent(iinventory.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) null, iinventory, itemstack, (EnumDirection) null);