content: package net.minecraft.server;
  
  import java.util.Iterator;
  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;
      }
  
      public static void a(DataConverterManager dataconvertermanager) {
          dataconvertermanager.a(DataConverterTypes.BLOCK_ENTITY, (DataInspector) (new DataInspectorItemList(TileEntityHopper.class, new String[] { "Items"})));
      }
  
      public void load(NBTTagCompound nbttagcompound) {
          super.load(nbttagcompound);
          this.items = NonNullList.a(this.getSize(), ItemStack.a);
          if (!this.c(nbttagcompound)) {
              ContainerUtil.b(nbttagcompound, this.items);
          }
  
          if (nbttagcompound.hasKeyOfType("CustomName", 8)) {
              this.o = nbttagcompound.getString("CustomName");
          }
  
          this.f = nbttagcompound.getInt("TransferCooldown");
      }
  
      public NBTTagCompound save(NBTTagCompound nbttagcompound) {
          super.save(nbttagcompound);
          if (!this.d(nbttagcompound)) {
              ContainerUtil.a(nbttagcompound, this.items);
          }
  
          nbttagcompound.setInt("TransferCooldown", this.f);
          if (this.hasCustomName()) {
              nbttagcompound.setString("CustomName", this.o);
          }
  
          return nbttagcompound;
      }
  
      public int getSize() {
          return this.items.size();
      }
  
      public ItemStack splitStack(int i, int j) {
          this.d((EntityHuman) null);
          ItemStack itemstack = ContainerUtil.a(this.q(), i, j);
  
          return itemstack;
      }
  
      public void setItem(int i, ItemStack itemstack) {
          this.d((EntityHuman) null);
          this.q().set(i, itemstack);
          if (itemstack.getCount() > this.getMaxStackSize()) {
              itemstack.setCount(this.getMaxStackSize());
          }
  
      }
  
      public String getName() {
          return this.hasCustomName() ? this.o : "container.hopper";
      }
  
      public int getMaxStackSize() {
          return maxStack; // CraftBukkit
      }
  
      public void e() {
          if (this.world != null && !this.world.isClientSide) {
              --this.f;
              this.g = this.world.getTime();
              if (!this.J()) {
                  this.setCooldown(0);
                  // Spigot start
                  if (!this.o() && this.world.spigotConfig.hopperCheck > 1) {
                      this.setCooldown(this.world.spigotConfig.hopperCheck);
                  }
                  // Spigot end
              }
  
          }
      }
  
      private boolean o() {
          mayAcceptItems = false; // Paper - at the beginning of a tick, assume we can't accept items
          if (this.world != null && !this.world.isClientSide) {
              if (!this.J() && BlockHopper.f(this.v())) {
                  boolean flag = false;
  
                  if (!this.p()) {
                      flag = this.s();
                  }
  
                  if (!this.r()) {
                      mayAcceptItems = true; // Paper - flag this hopper to be able to accept items
                      flag = a((IHopper) this) || flag;
                  }
  
                  if (flag) {
                      this.setCooldown(world.spigotConfig.hopperTransfer); // Spigot
                      this.update();
                      return true;
                  }
              }
              return false;
          } else {
              return false;
          }
      }
  
      // Paper start
      private boolean mayAcceptItems = false;
  
      public boolean canAcceptItems() {
          return mayAcceptItems;
      }
      // Paper end
  
      private boolean p() {
          Iterator iterator = this.items.iterator();
  
          ItemStack itemstack;
  
          do {
              if (!iterator.hasNext()) {
                  return true;
              }
  
              itemstack = (ItemStack) iterator.next();
          } while (itemstack.isEmpty());
  
          return false;
      }
  
      public boolean x_() {
          return this.p();
      }
  
      private boolean r() {
          Iterator iterator = this.items.iterator();
  
          ItemStack itemstack;
  
          do {
              if (!iterator.hasNext()) {
                  return true;
              }
  
              itemstack = (ItemStack) iterator.next();
          } while (!itemstack.isEmpty() && itemstack.getCount() == itemstack.getMaxStackSize());
  
          return false;
      }
  
      // Paper start - Optimize Hoppers
      private static boolean skipPullModeEventFire = false;
      private static boolean skipPushModeEventFire = false;
      static boolean skipHopperEvents = false;
  
      private boolean hopperPush(IInventory iinventory, EnumDirection enumdirection) {
          skipPushModeEventFire = skipHopperEvents;
          boolean foundItem = false;
          for (int i = 0; i < this.getSize(); ++i) {
              if (!this.getItem(i).isEmpty()) {
                  foundItem = true;
                  ItemStack origItemStack = this.getItem(i);
                  ItemStack itemstack = origItemStack;
  
                  final int origCount = origItemStack.getCount();
                  final int moved = Math.min(world.spigotConfig.hopperAmount, origCount);
                  origItemStack.setCount(moved);
  
                  // We only need to fire the event once to give protection plugins a chance to cancel this event
                  // Because nothing uses getItem, every event call should end up the same result.
                  if (!skipPushModeEventFire) {
                      itemstack = callPushMoveEvent(iinventory, itemstack);
                      if (itemstack == null) { // cancelled
                          origItemStack.setCount(origCount);
                          return false;
                      }
                  }
                  final ItemStack itemstack2 = addItem(this, iinventory, itemstack, enumdirection);
                  final int remaining = itemstack2.getCount();
                  if (remaining != moved) {
                      origItemStack = origItemStack.cloneItemStack();
                      origItemStack.setCount(origCount - moved + remaining);
                      this.setItem(i, origItemStack);
                      iinventory.update();
                      return true;
                  }
                  origItemStack.setCount(origCount);
              }
          }
          if (foundItem && world.paperConfig.cooldownHopperWhenFull) { // Inventory was full - cooldown
              this.setCooldown(world.spigotConfig.hopperTransfer);
          }
          return false;
      }
  
      private static boolean hopperPull(IHopper ihopper, IInventory iinventory, int i) {
          ItemStack origItemStack = iinventory.getItem(i);
          ItemStack itemstack = origItemStack;
          final int origCount = origItemStack.getCount();
          final World world = ihopper.getWorld();
          final int moved = Math.min(world.spigotConfig.hopperAmount, origCount);
          itemstack.setCount(moved);
  
          if (!skipPullModeEventFire) {
              itemstack = callPullMoveEvent(ihopper, iinventory, itemstack);
              if (itemstack == null) { // cancelled
                  origItemStack.setCount(origCount);
                  // Drastically improve performance by returning true.
                  // No plugin could of relied on the behavior of false as the other call
                  // site for IMIE did not exhibit the same behavior
                  return true;
              }
          }
  
          final ItemStack itemstack2 = addItem(iinventory, ihopper, itemstack, null);
          final int remaining = itemstack2.getCount();
          if (remaining != moved) {
              origItemStack = origItemStack.cloneItemStack();
              origItemStack.setCount(origCount - moved + remaining);
              IGNORE_TILE_UPDATES = true;
              iinventory.setItem(i, origItemStack);
              IGNORE_TILE_UPDATES = false;
              iinventory.update();
              return true;
          }
          origItemStack.setCount(origCount);
  
          if (world.paperConfig.cooldownHopperWhenFull) {
              cooldownHopper(ihopper);
          }
  
          return false;
      }
  
      private ItemStack callPushMoveEvent(IInventory iinventory, ItemStack itemstack) {
          Inventory destinationInventory = getInventory(iinventory);
          InventoryMoveItemEvent event = new InventoryMoveItemEvent(this.getOwner(false).getInventory(),
                  CraftItemStack.asCraftMirror(itemstack), destinationInventory, true);
          boolean result = event.callEvent();
          if (!event.calledGetItem && !event.calledSetItem) {
              skipPushModeEventFire = true;
          }
          if (!result) {
              cooldownHopper(this);
              return null;
          }
  
          if (event.calledSetItem) {
              return CraftItemStack.asNMSCopy(event.getItem());
          } else {
              return itemstack;
          }
      }
  
      private static ItemStack callPullMoveEvent(IHopper hopper, IInventory iinventory, ItemStack itemstack) {
          Inventory sourceInventory = getInventory(iinventory);
          Inventory destination = getInventory(hopper);
  
          InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory,
                  // Mirror is safe as we no plugins ever use this item
                  CraftItemStack.asCraftMirror(itemstack), destination, false);
          boolean result = event.callEvent();
          if (!event.calledGetItem && !event.calledSetItem) {
              skipPullModeEventFire = true;
          }
          if (!result) {
              cooldownHopper(hopper);
              return null;
          }
  
          if (event.calledSetItem) {
              return CraftItemStack.asNMSCopy(event.getItem());
          } else {
              return itemstack;
          }
      }
  
      private static Inventory getInventory(IInventory iinventory) {
          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 if (iinventory instanceof TileEntity) {
              sourceInventory = ((TileEntity) iinventory).getOwner(false).getInventory();
          } else {
              sourceInventory = iinventory.getOwner().getInventory();
          }
          return sourceInventory;
      }
  
      private static void cooldownHopper(IHopper hopper) {
          if (hopper instanceof TileEntityHopper) {
              ((TileEntityHopper) hopper).setCooldown(hopper.getWorld().spigotConfig.hopperTransfer);
          } else if (hopper instanceof EntityMinecartHopper) {
              ((EntityMinecartHopper) hopper).setCooldown(hopper.getWorld().spigotConfig.hopperTransfer / 2);
          }
      }
  
      // Paper end
      private boolean s() {
          IInventory iinventory = this.I();
  
          if (iinventory == null) {
              return false;
          } else {
              EnumDirection enumdirection = BlockHopper.b(this.v()).opposite();
  
              if (this.a(iinventory, enumdirection)) {
                  return false;
              } else {
                  return hopperPush(iinventory, enumdirection); /* // Paper - disable rest
                  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);
  
                          // CraftBukkit start - Call event when pushing items into other inventories
                          CraftItemStack oitemstack = CraftItemStack.asCraftMirror(this.splitStack(i, world.spigotConfig.hopperAmount)); // Spigot
  
                          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(world.spigotConfig.hopperTransfer); // Spigot
                              return false;
                          }
                          int origCount = event.getItem().getAmount(); // Spigot
                          ItemStack itemstack1 = addItem(this, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection);
  
                          if (itemstack1.isEmpty()) {
                              if (event.getItem().equals(oitemstack)) {
                                  iinventory.update();
                              } else {
                                  this.setItem(i, itemstack);
                              }
                              // CraftBukkit end
                              return true;
                          }
  
                          itemstack.subtract(origCount - itemstack1.getCount()); // Spigot
                          this.setItem(i, itemstack);
                      }
                  }
  
                  return false;*/ // Paper - end commenting out replaced block for Hopper Optimizations
              }
          }
      }
  
      private boolean a(IInventory iinventory, EnumDirection enumdirection) {
          if (iinventory instanceof IWorldInventory) {
              IWorldInventory iworldinventory = (IWorldInventory) iinventory;
              int[] aint = iworldinventory.getSlotsForFace(enumdirection);
              int[] aint1 = aint;
              int i = aint.length;
  
              for (int j = 0; j < i; ++j) {
                  int k = aint1[j];
                  ItemStack itemstack = iworldinventory.getItem(k);
  
                  if (itemstack.isEmpty() || itemstack.getCount() != itemstack.getMaxStackSize()) {
                      return false;
                  }
              }
          } else {
              int l = iinventory.getSize();
  
              for (int i1 = 0; i1 < l; ++i1) {
                  ItemStack itemstack1 = iinventory.getItem(i1);
  
                  if (itemstack1.isEmpty() || itemstack1.getCount() != itemstack1.getMaxStackSize()) {
                      return false;
                  }
              }
          }
  
          return true;
      }
  
      private static boolean b(IInventory iinventory, EnumDirection enumdirection) {
          if (iinventory instanceof IWorldInventory) {
              IWorldInventory iworldinventory = (IWorldInventory) iinventory;
              int[] aint = iworldinventory.getSlotsForFace(enumdirection);
              int[] aint1 = aint;
              int i = aint.length;
  
              for (int j = 0; j < i; ++j) {
                  int k = aint1[j];
  
                  if (!iworldinventory.getItem(k).isEmpty()) {
                      return false;
                  }
              }
          } else {
              int l = iinventory.getSize();
  
              for (int i1 = 0; i1 < l; ++i1) {
                  if (!iinventory.getItem(i1).isEmpty()) {
                      return false;
                  }
              }
          }
  
          return true;
      }
  
      // Paper start - split methods, and only do entity lookup if in pull mode
      public static boolean a(IHopper ihopper) {
          IInventory iinventory = getInventory(ihopper, !(ihopper instanceof TileEntityHopper) || !ihopper.getWorld().paperConfig.isHopperPushBased);
  
          return acceptItem(ihopper, iinventory);
      }
  
      public static boolean acceptItem(IHopper ihopper, IInventory iinventory) {
          // Paper end
  
          if (iinventory != null) {
              EnumDirection enumdirection = EnumDirection.DOWN;
  
              if (b(iinventory, enumdirection)) {
                  return false;
              }
              skipPullModeEventFire = skipHopperEvents; // Paper
  
              if (iinventory instanceof IWorldInventory) {
                  IWorldInventory iworldinventory = (IWorldInventory) iinventory;
                  int[] aint = iworldinventory.getSlotsForFace(enumdirection);
                  int[] aint1 = aint;
                  int i = aint.length;
  
                  for (int j = 0; j < i; ++j) {
                      int k = aint1[j];
  
                      if (a(ihopper, iinventory, k, enumdirection)) {
                          return true;
                      }
                  }
              } else {
                  int l = iinventory.getSize();
  
                  for (int i1 = 0; i1 < l; ++i1) {
                      if (a(ihopper, iinventory, i1, enumdirection)) {
                          return true;
                      }
                  }
              }
          } else if (!ihopper.getWorld().paperConfig.isHopperPushBased || !(ihopper instanceof TileEntityHopper)) { // Paper - only search for entities in 'pull mode'
              Iterator iterator = a(ihopper.getWorld(), ihopper.E(), ihopper.F(), ihopper.G()).iterator(); // Change getHopperLookupBoundingBox() if this ever changes
  
              while (iterator.hasNext()) {
                  EntityItem entityitem = (EntityItem) iterator.next();
  
                  if (a((IInventory) null, ihopper, entityitem)) {
                      return true;
                  }
              }
          }
  
          return false;
      }
  
      private static boolean a(IHopper ihopper, IInventory iinventory, int i, EnumDirection enumdirection) {
          ItemStack itemstack = iinventory.getItem(i);
  
          if (!itemstack.isEmpty() && b(iinventory, itemstack, i, enumdirection)) {
              return hopperPull(ihopper, iinventory, i); /* // Paper - disable rest
              ItemStack itemstack1 = itemstack.cloneItemStack();
              // 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, ihopper.getWorld().spigotConfig.hopperAmount)); // Spigot
  
              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(ihopper.getWorld().spigotConfig.hopperTransfer); // Spigot
                  } else if (ihopper instanceof EntityMinecartHopper) {
                      ((EntityMinecartHopper) ihopper).setCooldown(ihopper.getWorld().spigotConfig.hopperTransfer / 2); // Spigot
                  }
                  return false;
              }
              int origCount = event.getItem().getAmount(); // Spigot
              ItemStack itemstack2 = addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
  
              if (itemstack2.isEmpty()) {
                  if (event.getItem().equals(oitemstack)) {
                      iinventory.update();
                  } else {
                      iinventory.setItem(i, itemstack1);
                  }
                  // CraftBukkit end
                  return true;
              }
  
              itemstack1.subtract(origCount - itemstack2.getCount()); // Spigot
              iinventory.setItem(i, itemstack1);*/ // Paper - end commenting out replaced block for Hopper Optimizations
          }
  
          return false;
      }
  
      public static boolean putDropInInventory(IInventory iinventory, IInventory iinventory1, EntityItem entityitem) { return a(iinventory, iinventory1, entityitem); } // Paper - OBFHELPER
      public static boolean a(IInventory iinventory, IInventory iinventory1, EntityItem entityitem) {
          boolean flag = false;
  
          if (entityitem == null) {
              return false;
          } else {
              // CraftBukkit start
              InventoryPickupItemEvent event = new InventoryPickupItemEvent(getInventory(iinventory1), (org.bukkit.entity.Item) entityitem.getBukkitEntity()); // Paper - avoid snapshot creation
              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);
  
              if (itemstack1.isEmpty()) {
                  flag = true;
                  entityitem.die();
              } else {
                  entityitem.setItemStack(itemstack1);
              }
  
              return flag;
          }
      }
  
      public static ItemStack addItem(IInventory iinventory, IInventory iinventory1, ItemStack itemstack, @Nullable EnumDirection enumdirection) {
          if (iinventory1 instanceof IWorldInventory && enumdirection != null) {
              IWorldInventory iworldinventory = (IWorldInventory) iinventory1;
              int[] aint = iworldinventory.getSlotsForFace(enumdirection);
  
              for (int i = 0; i < aint.length && !itemstack.isEmpty(); ++i) {
                  itemstack = a(iinventory, iinventory1, itemstack, aint[i], enumdirection);
              }
          } else {
              int j = iinventory1.getSize();
  
              for (int k = 0; k < j && !itemstack.isEmpty(); ++k) {
                  itemstack = a(iinventory, iinventory1, itemstack, k, enumdirection);
              }
          }
  
          return itemstack;
      }
  
      private static boolean a(IInventory iinventory, ItemStack itemstack, int i, EnumDirection enumdirection) {
          return !iinventory.b(i, itemstack) ? false : !(iinventory instanceof IWorldInventory) || ((IWorldInventory) iinventory).canPlaceItemThroughFace(i, itemstack, enumdirection);
      }
  
      private static boolean b(IInventory iinventory, ItemStack itemstack, int i, EnumDirection enumdirection) {
          return !(iinventory instanceof IWorldInventory) || ((IWorldInventory) iinventory).canTakeItemThroughFace(i, itemstack, enumdirection);
      }
  
      private static ItemStack a(IInventory iinventory, IInventory iinventory1, ItemStack itemstack, int i, EnumDirection enumdirection) {
          ItemStack itemstack1 = iinventory1.getItem(i);
  
          if (a(iinventory1, itemstack, i, enumdirection)) {
              boolean flag = false;
              boolean flag1 = iinventory1.x_();
  
              if (itemstack1.isEmpty()) {
                  IGNORE_TILE_UPDATES = true; // Paper
                  iinventory1.setItem(i, itemstack);
                  IGNORE_TILE_UPDATES = false; // Paper
                  itemstack = ItemStack.a;
                  flag = true;
              } else if (a(itemstack1, itemstack)) {
                  int j = itemstack.getMaxStackSize() - itemstack1.getCount();
                  int k = Math.min(itemstack.getCount(), j);
  
                  itemstack.subtract(k);
                  itemstack1.add(k);
                  flag = k > 0;
              }
  
              if (flag) {
                  if (flag1 && iinventory1 instanceof TileEntityHopper) {
                      TileEntityHopper tileentityhopper = (TileEntityHopper) iinventory1;
  
                      if (!tileentityhopper.K()) {
                          byte b0 = 0;
  
                          if (iinventory != null && iinventory instanceof TileEntityHopper) {
                              TileEntityHopper tileentityhopper1 = (TileEntityHopper) iinventory;
  
                              if (tileentityhopper.g >= tileentityhopper1.g) {
                                  b0 = 1;
                              }
                          }
  
                          tileentityhopper.setCooldown(tileentityhopper.world.spigotConfig.hopperTransfer - b0); // Spigot
                      }
                  }
  
                  iinventory1.update();
              }
          }
  
          return itemstack;
      }
  
      private IInventory I() {
          EnumDirection enumdirection = BlockHopper.b(this.v());
  
          // Paper start - don't search for entities in push mode
          World world = getWorld();
          return getInventory(world, this.E() + (double) enumdirection.getAdjacentX(), this.F() + (double) enumdirection.getAdjacentY(), this.G() + (double) enumdirection.getAdjacentZ(), !world.paperConfig.isHopperPushBased);
          // Paper end
      }
  
      // Paper start - add option to search for entities
      public static IInventory b(IHopper hopper) {
          return getInventory(hopper, true);
      }
  
      public static IInventory getInventory(IHopper ihopper, boolean searchForEntities) {
          return getInventory(ihopper.getWorld(), ihopper.E(), ihopper.F() + 1.0D, ihopper.G(), searchForEntities);
          // Paper end
      }
  
      public static List<EntityItem> a(World world, double d0, double d1, double d2) {
          return world.a(EntityItem.class, new AxisAlignedBB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D), IEntitySelector.a); // Change getHopperLookupBoundingBox(double, double, double) if the bounding box calculation is ever changed
      }
  
      // Paper start
      public AxisAlignedBB getHopperLookupBoundingBox() {
          return getHopperLookupBoundingBox(this.getX(), this.getY(), this.getZ());
      }
  
      private static AxisAlignedBB getHopperLookupBoundingBox(double d0, double d1, double d2) {
          // Change this if a(World, double, double, double) above ever changes
          return new AxisAlignedBB(d0 - 0.5D, d1, d2 - 0.5D, d0 + 0.5D, d1 + 1.5D, d2 + 0.5D);
      }
      // Paper end
  
      // Paper start - add option to searchForEntities
      public static IInventory b(World world, double d0, double d1, double d2) {
          return getInventory(world, d0, d1, d2, true);
      }
  
      public static IInventory getInventory(World world, double d0, double d1, double d2, boolean searchForEntities) {
          // Paper end
          Object object = null;
          int i = MathHelper.floor(d0);
          int j = MathHelper.floor(d1);
          int k = MathHelper.floor(d2);
          BlockPosition blockposition = new BlockPosition(i, j, k);
          if ( !world.isLoaded( blockposition ) ) return null; // Spigot
          Block block = world.getType(blockposition).getBlock();
  
          if (block.isTileEntity()) {
              TileEntity tileentity = world.getTileEntity(blockposition);
  
              if (tileentity instanceof IInventory) {
                  object = (IInventory) tileentity;
                  if (object instanceof TileEntityChest && block instanceof BlockChest) {
                      object = ((BlockChest) block).a(world, blockposition, true);
                  }
              }
          }
  		
  		net.minecraft.server.Chunk chunk = world.getChunkAtWorldCoords(blockposition);
  
          if (object == null && searchForEntities && !org.bukkit.craftbukkit.util.CraftMagicNumbers.getMaterial(block).isOccluding() && chunk.getItemCount(blockposition) > 0) { // Paper - only if searchForEntities
              List list = world.getEntities((Entity) null, new AxisAlignedBB(d0 - 0.5D, d1 - 0.5D, d2 - 0.5D, d0 + 0.5D, d1 + 0.5D, d2 + 0.5D), IEntitySelector.c);
  
              if (!list.isEmpty()) {
                  object = (IInventory) list.get(world.random.nextInt(list.size()));
              }
          }
  
          return (IInventory) object;
      }
  
      private static boolean a(ItemStack itemstack, ItemStack itemstack1) {
          return itemstack.getItem() != itemstack1.getItem() ? false : (itemstack.getData() != itemstack1.getData() ? false : (itemstack.getCount() > itemstack.getMaxStackSize() ? false : ItemStack.equals(itemstack, itemstack1)));
      }
  
      public double E() {
          return (double) this.position.getX() + 0.5D;
      }
  
      public double F() {
          return (double) this.position.getY() + 0.5D;
      }
  
      public double G() {
          return (double) this.position.getZ() + 0.5D;
      }
  
      private void setCooldown(int i) {
          this.f = i;
      }
  
      private boolean J() {
          return this.f > 0;
      }
  
      private boolean K() {
          return this.f > 8;
      }
  
      public String getContainerName() {
          return "minecraft:hopper";
      }
  
      public Container createContainer(PlayerInventory playerinventory, EntityHuman entityhuman) {
          this.d(entityhuman);
          return new ContainerHopper(playerinventory, this, entityhuman);
      }
  
      protected NonNullList<ItemStack> q() {
          return this.items;
      }
  }
  