From e0fae5ef02dc02f0f096da1046f9f64aaf001a6c Mon Sep 17 00:00:00 2001
From: Bjarne Koll <git@lynxplay.dev>
Date: Fri, 13 Dec 2024 19:02:07 +0100
Subject: [PATCH] Part of block entities

---
 .../block/entity/BarrelBlockEntity.java.patch |  29 +-
 .../BaseContainerBlockEntity.java.patch       |  28 +-
 .../block/entity/BellBlockEntity.java.patch   |  45 ++-
 .../block/entity/ChestBlockEntity.java.patch  |  39 +++
 .../ChiseledBookShelfBlockEntity.java.patch   |  49 +--
 .../entity/CommandBlockEntity.java.patch      |  25 ++
 .../block/entity/HopperBlockEntity.java.patch | 303 ++++++++++++++++
 .../SculkCatalystBlockEntity.java.patch       |  21 ++
 .../TheEndGatewayBlockEntity.java.patch       |  11 +
 .../block/entity/ChestBlockEntity.java.patch  |  51 ---
 .../entity/CommandBlockEntity.java.patch      |  26 --
 .../block/entity/HopperBlockEntity.java.patch | 322 ------------------
 .../SculkCatalystBlockEntity.java.patch       |  29 --
 .../TheEndGatewayBlockEntity.java.patch       |  19 --
 14 files changed, 461 insertions(+), 536 deletions(-)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch (52%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch (75%)
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch (50%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
 rename paper-server/patches/{unapplied => sources}/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch (55%)
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
 create mode 100644 paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
 delete mode 100644 paper-server/patches/unapplied/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch

diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
similarity index 52%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
index bc89832782..11854ff324 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BarrelBlockEntity.java.patch
@@ -1,45 +1,36 @@
 --- a/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/BarrelBlockEntity.java
-@@ -20,9 +20,49 @@
- import net.minecraft.world.level.Level;
- import net.minecraft.world.level.block.BarrelBlock;
+@@ -21,6 +_,40 @@
  import net.minecraft.world.level.block.state.BlockState;
-+// CraftBukkit start
-+import java.util.ArrayList;
-+import java.util.List;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
  
  public class BarrelBlockEntity extends RandomizableContainerBlockEntity {
- 
 +    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new ArrayList<>();
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = MAX_STACK;
 +
 +    @Override
-+    public List<ItemStack> getContents() {
++    public java.util.List<ItemStack> getContents() {
 +        return this.items;
 +    }
 +
 +    @Override
-+    public void onOpen(CraftHumanEntity who) {
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.add(who);
 +    }
 +
 +    @Override
-+    public void onClose(CraftHumanEntity who) {
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.remove(who);
 +    }
 +
 +    @Override
-+    public List<HumanEntity> getViewers() {
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
 +    @Override
 +    public int getMaxStackSize() {
-+       return this.maxStack;
++        return this.maxStack;
 +    }
 +
 +    @Override
@@ -47,6 +38,6 @@
 +        this.maxStack = i;
 +    }
 +    // CraftBukkit end
-     private NonNullList<ItemStack> items;
-     public final ContainerOpenersCounter openersCounter;
- 
+     private NonNullList<ItemStack> items = NonNullList.withSize(27, ItemStack.EMPTY);
+     private final ContainerOpenersCounter openersCounter = new ContainerOpenersCounter() {
+         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
similarity index 75%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
index 07ed6af225..09da60cff3 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java.patch
@@ -1,28 +1,28 @@
 --- a/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/BaseContainerBlockEntity.java
-@@ -73,17 +73,44 @@
+@@ -68,17 +_,44 @@
      protected abstract Component getDefaultName();
  
      public boolean canOpen(Player player) {
--        return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName());
-+        return BaseContainerBlockEntity.canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent
+-        return canUnlock(player, this.lockKey, this.getDisplayName());
++        return canUnlock(player, this.lockKey, this.getDisplayName(), this); // Paper - Add BlockLockCheckEvent
      }
  
 +    @Deprecated @io.papermc.paper.annotation.DoNotUse // Paper - Add BlockLockCheckEvent
-     public static boolean canUnlock(Player player, LockCode lock, Component containerName) {
+     public static boolean canUnlock(Player player, LockCode code, Component displayName) {
 +        // Paper start - Add BlockLockCheckEvent
-+        return canUnlock(player, lock, containerName, null);
++        return canUnlock(player, code, displayName, null);
 +    }
-+    public static boolean canUnlock(Player player, LockCode lock, Component containerName, @Nullable BlockEntity blockEntity) {
++    public static boolean canUnlock(Player player, LockCode code, Component displayName, @Nullable BlockEntity blockEntity) {
 +        if (player instanceof net.minecraft.server.level.ServerPlayer serverPlayer && blockEntity != null && blockEntity.getLevel() != null && blockEntity.getLevel().getBlockEntity(blockEntity.getBlockPos()) == blockEntity) {
 +            final org.bukkit.block.Block block = org.bukkit.craftbukkit.block.CraftBlock.at(blockEntity.getLevel(), blockEntity.getBlockPos());
-+            net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(containerName));
++            net.kyori.adventure.text.Component lockedMessage = net.kyori.adventure.text.Component.translatable("container.isLocked", io.papermc.paper.adventure.PaperAdventure.asAdventure(displayName));
 +            net.kyori.adventure.sound.Sound lockedSound = net.kyori.adventure.sound.Sound.sound(org.bukkit.Sound.BLOCK_CHEST_LOCKED, net.kyori.adventure.sound.Sound.Source.BLOCK, 1.0F, 1.0F);
 +            final io.papermc.paper.event.block.BlockLockCheckEvent event = new io.papermc.paper.event.block.BlockLockCheckEvent(block, serverPlayer.getBukkitEntity(), lockedMessage, lockedSound);
 +            event.callEvent();
 +            if (event.getResult() == org.bukkit.event.Event.Result.ALLOW) {
 +                return true;
-+            } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !lock.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) {
++            } else if (event.getResult() == org.bukkit.event.Event.Result.DENY || (!player.isSpectator() && !code.unlocksWith(event.isUsingCustomKeyItemStack() ? org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getKeyItem()) : player.getMainHandItem()))) {
 +                if (event.getLockedMessage() != null) {
 +                    event.getPlayer().sendActionBar(event.getLockedMessage());
 +                }
@@ -35,9 +35,9 @@
 +            }
 +        } else { // logic below is replaced by logic above
 +        // Paper end - Add BlockLockCheckEvent
-         if (!player.isSpectator() && !lock.unlocksWith(player.getMainHandItem())) {
--            player.displayClientMessage(Component.translatable("container.isLocked", containerName), true);
-+            player.displayClientMessage(Component.translatable("container.isLocked", containerName), true); // Paper - diff on change
+         if (!player.isSpectator() && !code.unlocksWith(player.getMainHandItem())) {
+-            player.displayClientMessage(Component.translatable("container.isLocked", displayName), true);
++            player.displayClientMessage(Component.translatable("container.isLocked", displayName), true); // Paper - diff on change
              player.playNotifySound(SoundEvents.CHEST_LOCKED, SoundSource.BLOCKS, 1.0F, 1.0F);
              return false;
          } else {
@@ -47,9 +47,9 @@
      }
  
      protected abstract NonNullList<ItemStack> getItems();
-@@ -178,4 +205,12 @@
-         nbt.remove("lock");
-         nbt.remove("Items");
+@@ -166,4 +_,12 @@
+         tag.remove("lock");
+         tag.remove("Items");
      }
 +
 +    // CraftBukkit start
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
similarity index 50%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
index c8f5ced60a..1cc3771a2f 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/BellBlockEntity.java.patch
@@ -1,6 +1,6 @@
 --- a/net/minecraft/world/level/block/entity/BellBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/BellBlockEntity.java
-@@ -63,6 +63,11 @@
+@@ -60,6 +_,11 @@
  
          if (blockEntity.ticks >= 50) {
              blockEntity.shaking = false;
@@ -12,53 +12,48 @@
              blockEntity.ticks = 0;
          }
  
-@@ -76,6 +81,7 @@
-                 ++blockEntity.resonationTicks;
+@@ -73,6 +_,7 @@
+                 blockEntity.resonationTicks++;
              } else {
-                 bellEffect.run(world, pos, blockEntity.nearbyEntities);
+                 resonationEndAction.run(level, pos, blockEntity.nearbyEntities);
 +                blockEntity.nearbyEntities.clear(); // Paper - Fix bell block entity memory leak
                  blockEntity.resonating = false;
              }
          }
-@@ -120,11 +126,12 @@
-                 LivingEntity entityliving = (LivingEntity) iterator.next();
- 
-                 if (entityliving.isAlive() && !entityliving.isRemoved() && blockposition.closerToCenterThan(entityliving.position(), 32.0D)) {
--                    entityliving.getBrain().setMemory(MemoryModuleType.HEARD_BELL_TIME, (Object) this.level.getGameTime());
-+                    entityliving.getBrain().setMemory(MemoryModuleType.HEARD_BELL_TIME, this.level.getGameTime()); // CraftBukkit - decompile error
+@@ -113,6 +_,8 @@
                  }
              }
          }
- 
++
 +        this.nearbyEntities.removeIf(e -> !e.isAlive()); // Paper - Fix bell block entity memory leak
      }
  
-     private static boolean areRaidersNearby(BlockPos pos, List<LivingEntity> hearingEntities) {
-@@ -144,9 +151,13 @@
+     private static boolean areRaidersNearby(BlockPos pos, List<LivingEntity> raiders) {
+@@ -129,7 +_,10 @@
      }
  
-     private static void makeRaidersGlow(Level world, BlockPos pos, List<LivingEntity> hearingEntities) {
-+        List<org.bukkit.entity.LivingEntity> entities = // CraftBukkit
-         hearingEntities.stream().filter((entityliving) -> {
-             return BellBlockEntity.isRaiderWithinRange(pos, entityliving);
--        }).forEach(BellBlockEntity::glow);
-+        }).map((entity) -> (org.bukkit.entity.LivingEntity) entity.getBukkitEntity()).collect(java.util.stream.Collectors.toCollection(java.util.ArrayList::new)); // CraftBukkit
-+
-+        org.bukkit.craftbukkit.event.CraftEventFactory.handleBellResonateEvent(world, pos, entities).forEach(entity -> glow(entity, pos)); // Paper - Add BellRevealRaiderEvent
-+        // CraftBukkit end
+     private static void makeRaidersGlow(Level level, BlockPos pos, List<LivingEntity> raiders) {
+-        raiders.stream().filter(raider -> isRaiderWithinRange(pos, raider)).forEach(BellBlockEntity::glow);
++        // Paper start - call bell resonate event and bell reveal raider event
++        final List<org.bukkit.entity.LivingEntity> inRangeRaiders = raiders.stream().filter(raider -> isRaiderWithinRange(pos, raider)).map(e -> e.getBukkitEntity()).toList();
++        org.bukkit.craftbukkit.event.CraftEventFactory.handleBellResonateEvent(level, pos, inRangeRaiders).forEach(e -> glow(e, pos));
++        // Paper end - call bell resonate event and bell reveal raider event
      }
  
-     private static void showBellParticles(Level world, BlockPos pos, List<LivingEntity> hearingEntities) {
-@@ -178,6 +189,13 @@
+     private static void showBellParticles(Level level, BlockPos pos, List<LivingEntity> raiders) {
+@@ -159,7 +_,16 @@
+         return raider.isAlive() && !raider.isRemoved() && pos.closerToCenterThan(raider.position(), 48.0) && raider.getType().is(EntityTypeTags.RAIDERS);
      }
  
++    @io.papermc.paper.annotation.DoNotUse // Paper - Add BellRevealRaiderEvent
      private static void glow(LivingEntity entity) {
 +        // Paper start - Add BellRevealRaiderEvent
 +        glow(entity, null);
 +    }
 +
 +    private static void glow(LivingEntity entity, @javax.annotation.Nullable BlockPos pos) {
-+        if (pos != null && !new io.papermc.paper.event.block.BellRevealRaiderEvent(org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), pos), (org.bukkit.entity.Raider) entity.getBukkitEntity()).callEvent()) return;
++        if (pos != null && !new io.papermc.paper.event.block.BellRevealRaiderEvent(org.bukkit.craftbukkit.block.CraftBlock.at(entity.level(), pos), (org.bukkit.entity.Raider) entity.getBukkitEntity()).callEvent())
++            return;
 +        // Paper end - Add BellRevealRaiderEvent
          entity.addEffect(new MobEffectInstance(MobEffects.GLOWING, 60));
      }
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
new file mode 100644
index 0000000000..d3845d7cfa
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
@@ -0,0 +1,39 @@
+--- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
+@@ -56,6 +_,36 @@
+     };
+     private final ChestLidController chestLidController = new ChestLidController();
+ 
++    // CraftBukkit start - add fields and methods
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++    private int maxStack = MAX_STACK;
++
++    public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
++        return this.items;
++    }
++
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.add(who);
++    }
++
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.remove(who);
++    }
++
++    public java.util.List<org.bukkit.entity.HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++    }
++    // CraftBukkit end
++
+     protected ChestBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState blockState) {
+         super(type, pos, blockState);
+     }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
similarity index 55%
rename from paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
rename to paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
index af35923253..bb3e767def 100644
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java.patch
@@ -1,43 +1,30 @@
 --- a/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
 +++ b/net/minecraft/world/level/block/entity/ChiseledBookShelfBlockEntity.java
-@@ -23,13 +23,55 @@
- import net.minecraft.world.level.gameevent.GameEvent;
- import org.slf4j.Logger;
+@@ -27,6 +_,42 @@
+     private final NonNullList<ItemStack> items = NonNullList.withSize(6, ItemStack.EMPTY);
+     private int lastInteractedSlot = -1;
  
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.Location;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
-+
- public class ChiseledBookShelfBlockEntity extends BlockEntity implements Container {
- 
-     public static final int MAX_BOOKS_IN_STORAGE = 6;
-     private static final Logger LOGGER = LogUtils.getLogger();
-     private final NonNullList<ItemStack> items;
-     public int lastInteractedSlot;
 +    // CraftBukkit start - add fields and methods
-+    public List<HumanEntity> transaction = new java.util.ArrayList<>();
++    public java.util.List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
 +    private int maxStack = 1;
- 
++
 +    @Override
-+    public List<ItemStack> getContents() {
++    public java.util.List<net.minecraft.world.item.ItemStack> getContents() {
 +        return this.items;
 +    }
 +
 +    @Override
-+    public void onOpen(CraftHumanEntity who) {
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.add(who);
 +    }
 +
 +    @Override
-+    public void onClose(CraftHumanEntity who) {
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
 +        this.transaction.remove(who);
 +    }
 +
 +    @Override
-+    public List<HumanEntity> getViewers() {
++    public List<org.bukkit.entity.HumanEntity> getViewers() {
 +        return this.transaction;
 +    }
 +
@@ -47,25 +34,25 @@
 +    }
 +
 +    @Override
-+    public Location getLocation() {
++    public org.bukkit.Location getLocation() {
 +        if (this.level == null) return null;
-+        return new org.bukkit.Location(this.level.getWorld(), this.worldPosition.getX(), this.worldPosition.getY(), this.worldPosition.getZ());
++        return io.papermc.paper.util.MCUtil.toLocation(this.level, this.worldPosition);
 +    }
 +    // CraftBukkit end
 +
      public ChiseledBookShelfBlockEntity(BlockPos pos, BlockState state) {
          super(BlockEntityType.CHISELED_BOOKSHELF, pos, state);
-         this.items = NonNullList.withSize(6, ItemStack.EMPTY);
-@@ -100,7 +142,7 @@
- 
+     }
+@@ -93,7 +_,7 @@
+         ItemStack itemStack = Objects.requireNonNullElse(this.items.get(slot), ItemStack.EMPTY);
          this.items.set(slot, ItemStack.EMPTY);
-         if (!itemstack.isEmpty()) {
+         if (!itemStack.isEmpty()) {
 -            this.updateState(slot);
 +            if (this.level != null) this.updateState(slot); // CraftBukkit - SPIGOT-7381: check for null world
          }
  
-         return itemstack;
-@@ -115,7 +157,7 @@
+         return itemStack;
+@@ -108,7 +_,7 @@
      public void setItem(int slot, ItemStack stack) {
          if (stack.is(ItemTags.BOOKSHELF_BOOKS)) {
              this.items.set(slot, stack);
@@ -74,7 +61,7 @@
          } else if (stack.isEmpty()) {
              this.removeItem(slot, 1);
          }
-@@ -131,7 +173,7 @@
+@@ -124,7 +_,7 @@
  
      @Override
      public int getMaxStackSize() {
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
new file mode 100644
index 0000000000..dca791d38d
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
@@ -0,0 +1,25 @@
+--- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java
+@@ -21,6 +_,13 @@
+     private boolean auto;
+     private boolean conditionMet;
+     private final BaseCommandBlock commandBlock = new BaseCommandBlock() {
++        // CraftBukkit start
++        @Override
++        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
++            return new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, CommandBlockEntity.this);
++        }
++        // CraftBukkit end
++
+         @Override
+         public void setCommand(String command) {
+             super.setCommand(command);
+@@ -51,7 +_,7 @@
+                 Vec3.atCenterOf(CommandBlockEntity.this.worldPosition),
+                 new Vec2(0.0F, direction.toYRot()),
+                 this.getLevel(),
+-                2,
++                this.getLevel().paperConfig().commandBlocks.permissionsLevel, // Paper - configurable command block perm level
+                 this.getName().getString(),
+                 this.getName(),
+                 this.getLevel().getServer(),
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
new file mode 100644
index 0000000000..9f296844d6
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
@@ -0,0 +1,303 @@
+--- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
+@@ -37,6 +_,37 @@
+     private long tickedGameTime;
+     private Direction facing;
+ 
++    // CraftBukkit start - add fields and methods
++    public List<org.bukkit.entity.HumanEntity> transaction = new java.util.ArrayList<>();
++    private int maxStack = MAX_STACK;
++
++    public List<ItemStack> getContents() {
++        return this.items;
++    }
++
++    public void onOpen(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.add(who);
++    }
++
++    public void onClose(org.bukkit.craftbukkit.entity.CraftHumanEntity who) {
++        this.transaction.remove(who);
++    }
++
++    public List<org.bukkit.entity.HumanEntity> getViewers() {
++        return this.transaction;
++    }
++
++    @Override
++    public int getMaxStackSize() {
++        return this.maxStack;
++    }
++
++    public void setMaxStackSize(int size) {
++        this.maxStack = size;
++    }
++    // CraftBukkit end
++
++
+     public HopperBlockEntity(BlockPos pos, BlockState blockState) {
+         super(BlockEntityType.HOPPER, pos, blockState);
+         this.facing = blockState.getValue(HopperBlock.FACING);
+@@ -97,7 +_,14 @@
+         blockEntity.tickedGameTime = level.getGameTime();
+         if (!blockEntity.isOnCooldown()) {
+             blockEntity.setCooldown(0);
+-            tryMoveItems(level, pos, state, blockEntity, () -> suckInItems(level, blockEntity));
++            // Spigot start
++            boolean result = tryMoveItems(level, pos, state, blockEntity, () -> {
++                return suckInItems(level, blockEntity);
++            });
++            if (!result && blockEntity.level.spigotConfig.hopperCheck > 1) {
++                blockEntity.setCooldown(blockEntity.level.spigotConfig.hopperCheck);
++            }
++            // Spigot end
+         }
+     }
+ 
+@@ -116,7 +_,7 @@
+                 }
+ 
+                 if (flag) {
+-                    blockEntity.setCooldown(8);
++                    blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot
+                     setChanged(level, pos, state);
+                     return true;
+                 }
+@@ -149,14 +_,47 @@
+                     ItemStack item = blockEntity.getItem(i);
+                     if (!item.isEmpty()) {
+                         int count = item.getCount();
+-                        ItemStack itemStack = addItem(blockEntity, attachedContainer, blockEntity.removeItem(i, 1), opposite);
++                        // CraftBukkit start - Call event when pushing items into other inventories
++                        ItemStack original = item.copy();
++                        org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(
++                            blockEntity.removeItem(i, level.spigotConfig.hopperAmount)
++                        ); // Spigot
++
++                        org.bukkit.inventory.Inventory destinationInventory;
++                        // Have to special case large chests as they work oddly
++                        if (attachedContainer instanceof final CompoundContainer compoundContainer) {
++                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
++                        } else if (attachedContainer.getOwner() != null) {
++                            destinationInventory = attachedContainer.getOwner().getInventory();
++                        } else {
++                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(attachedContainer);
++                        }
++
++                        org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(
++                            blockEntity.getOwner().getInventory(),
++                            oitemstack,
++                            destinationInventory,
++                            true
++                        );
++                        if (!event.callEvent()) {
++                            blockEntity.setItem(i, original);
++                            blockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot
++                            return false;
++                        }
++                        int origCount = event.getItem().getAmount(); // Spigot
++                        ItemStack itemStack = HopperBlockEntity.addItem(blockEntity, attachedContainer, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), opposite);
++                        // CraftBukkit end
++
+                         if (itemStack.isEmpty()) {
+                             attachedContainer.setChanged();
+                             return true;
+                         }
+ 
+                         item.setCount(count);
+-                        if (count == 1) {
++                        // Spigot start
++                        item.shrink(origCount - itemStack.getCount());
++                        if (count <= level.spigotConfig.hopperAmount) {
++                            // Spigot end
+                             blockEntity.setItem(i, item);
+                         }
+                     }
+@@ -219,7 +_,7 @@
+             Direction direction = Direction.DOWN;
+ 
+             for (int i : getSlots(sourceContainer, direction)) {
+-                if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction)) {
++                if (tryTakeInItemFromSlot(hopper, sourceContainer, i, direction, level)) { // Spigot
+                     return true;
+                 }
+             }
+@@ -239,18 +_,54 @@
+         }
+     }
+ 
+-    private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction) {
++    private static boolean tryTakeInItemFromSlot(Hopper hopper, Container container, int slot, Direction direction, Level level) { // Spigot
+         ItemStack item = container.getItem(slot);
+         if (!item.isEmpty() && canTakeItemFromContainer(hopper, container, item, slot, direction)) {
+             int count = item.getCount();
+-            ItemStack itemStack = addItem(container, hopper, container.removeItem(slot, 1), null);
++            // CraftBukkit start - Call event on collection of items from inventories into the hopper
++            ItemStack original = item.copy();
++            org.bukkit.craftbukkit.inventory.CraftItemStack oitemstack = org.bukkit.craftbukkit.inventory.CraftItemStack.asCraftMirror(
++                container.removeItem(slot, level.spigotConfig.hopperAmount) // Spigot
++            );
++
++            org.bukkit.inventory.Inventory sourceInventory;
++            // Have to special case large chests as they work oddly
++            if (container instanceof final CompoundContainer compoundContainer) {
++                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest(compoundContainer);
++            } else if (container.getOwner() != null) {
++                sourceInventory = container.getOwner().getInventory();
++            } else {
++                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventory(container);
++            }
++
++            org.bukkit.event.inventory.InventoryMoveItemEvent event = new org.bukkit.event.inventory.InventoryMoveItemEvent(
++                sourceInventory,
++                oitemstack,
++                hopper.getOwner().getInventory(),
++                false
++            );
++
++            if (!event.callEvent()) {
++                container.setItem(slot, original);
++
++                if (hopper instanceof final HopperBlockEntity hopperBlockEntity) {
++                    hopperBlockEntity.setCooldown(level.spigotConfig.hopperTransfer); // Spigot
++                }
++
++                return false;
++            }
++            int origCount = event.getItem().getAmount(); // Spigot
++            ItemStack itemStack = HopperBlockEntity.addItem(container, hopper, org.bukkit.craftbukkit.inventory.CraftItemStack.asNMSCopy(event.getItem()), null);
++            // CraftBukkit end
++
+             if (itemStack.isEmpty()) {
+                 container.setChanged();
+                 return true;
+             }
+ 
+             item.setCount(count);
+-            if (count == 1) {
++            item.shrink(origCount - itemStack.getCount());
++            if (count <= level.spigotConfig.hopperAmount) {
+                 container.setItem(slot, item);
+             }
+         }
+@@ -260,12 +_,21 @@
+ 
+     public static boolean addItem(Container container, ItemEntity item) {
+         boolean flag = false;
++        // CraftBukkit start
++        org.bukkit.event.inventory.InventoryPickupItemEvent event = new org.bukkit.event.inventory.InventoryPickupItemEvent(
++            container.getOwner().getInventory(), (org.bukkit.entity.Item) item.getBukkitEntity()
++        );
++        item.level().getCraftServer().getPluginManager().callEvent(event);
++        if (event.isCancelled()) {
++            return false;
++        }
++        // CraftBukkit end
+         ItemStack itemStack = item.getItem().copy();
+         ItemStack itemStack1 = addItem(null, container, itemStack, null);
+         if (itemStack1.isEmpty()) {
+             flag = true;
+             item.setItem(ItemStack.EMPTY);
+-            item.discard();
++            item.discard(org.bukkit.event.entity.EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
+         } else {
+             item.setItem(itemStack1);
+         }
+@@ -307,11 +_,18 @@
+             boolean flag = false;
+             boolean isEmpty = destination.isEmpty();
+             if (item.isEmpty()) {
++                // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
++                ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size
++                if (!stack.isEmpty() && stack.getCount() > destination.getMaxStackSize()) {
++                    leftover = stack; // Paper - Make hoppers respect inventory max stack size
++                    stack = stack.split(destination.getMaxStackSize());
++                }
++                // Spigot end
+                 destination.setItem(slot, stack);
+-                stack = ItemStack.EMPTY;
++                stack = leftover; // Paper - Make hoppers respect inventory max stack size
+                 flag = true;
+             } else if (canMergeItems(item, stack)) {
+-                int i = stack.getMaxStackSize() - item.getCount();
++                int i = Math.min(stack.getMaxStackSize(), destination.getMaxStackSize()) - item.getCount(); // Paper - Make hoppers respect inventory max stack size
+                 int min = Math.min(stack.getCount(), i);
+                 stack.shrink(min);
+                 item.grow(min);
+@@ -325,7 +_,7 @@
+                         min = 1;
+                     }
+ 
+-                    hopperBlockEntity.setCooldown(8 - min);
++                    hopperBlockEntity.setCooldown(hopperBlockEntity.level.spigotConfig.hopperTransfer - min); // Spigot
+                 }
+ 
+                 destination.setChanged();
+@@ -335,14 +_,57 @@
+         return stack;
+     }
+ 
++    // CraftBukkit start
++    @Nullable
++    private static Container runHopperInventorySearchEvent(
++        Container container,
++        org.bukkit.craftbukkit.block.CraftBlock hopper,
++        org.bukkit.craftbukkit.block.CraftBlock searchLocation,
++        org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType containerType
++    ) {
++        org.bukkit.event.inventory.HopperInventorySearchEvent event = new org.bukkit.event.inventory.HopperInventorySearchEvent(
++            (container != null) ? new org.bukkit.craftbukkit.inventory.CraftInventory(container) : null,
++            containerType,
++            hopper,
++            searchLocation
++        );
++        event.callEvent();
++        return (event.getInventory() != null) ? ((org.bukkit.craftbukkit.inventory.CraftInventory) event.getInventory()).getInventory() : null;
++    }
++    // CraftBukkit end
++
+     @Nullable
+     private static Container getAttachedContainer(Level level, BlockPos pos, HopperBlockEntity blockEntity) {
+-        return getContainerAt(level, pos.relative(blockEntity.facing));
++        // CraftBukkit start
++        BlockPos searchPosition = pos.relative(blockEntity.facing);
++        Container inventory = getContainerAt(level, searchPosition);
++
++        org.bukkit.craftbukkit.block.CraftBlock hopper = org.bukkit.craftbukkit.block.CraftBlock.at(level, pos);
++        org.bukkit.craftbukkit.block.CraftBlock searchBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, searchPosition);
++        return HopperBlockEntity.runHopperInventorySearchEvent(
++            inventory,
++            hopper,
++            searchBlock,
++            org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType.DESTINATION
++        );
++        // CraftBukkit end
+     }
+ 
+     @Nullable
+     private static Container getSourceContainer(Level level, Hopper hopper, BlockPos pos, BlockState state) {
+-        return getContainerAt(level, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0, hopper.getLevelZ());
++        // CraftBukkit start
++        final Container inventory = HopperBlockEntity.getContainerAt(level, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ());
++
++        final BlockPos blockPosition = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY(), hopper.getLevelZ());
++        org.bukkit.craftbukkit.block.CraftBlock hopperBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPosition);
++        org.bukkit.craftbukkit.block.CraftBlock containerBlock = org.bukkit.craftbukkit.block.CraftBlock.at(level, blockPosition.above());
++        return HopperBlockEntity.runHopperInventorySearchEvent(
++            inventory,
++            hopperBlock,
++            containerBlock,
++            org.bukkit.event.inventory.HopperInventorySearchEvent.ContainerType.SOURCE
++        );
++        // CraftBukkit end
+     }
+ 
+     public static List<ItemEntity> getItemsAtAndAbove(Level level, Hopper hopper) {
+@@ -367,6 +_,7 @@
+ 
+     @Nullable
+     private static Container getBlockContainer(Level level, BlockPos pos, BlockState state) {
++        if (!level.spigotConfig.hopperCanLoadChunks && !level.hasChunkAt(pos)) return null; // Spigot
+         Block block = state.getBlock();
+         if (block instanceof WorldlyContainerHolder) {
+             return ((WorldlyContainerHolder)block).getContainer(state, level, pos);
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
new file mode 100644
index 0000000000..657dcc0967
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
@@ -0,0 +1,21 @@
+--- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
+@@ -34,8 +_,18 @@
+         this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(blockState, new BlockPositionSource(pos));
+     }
+ 
++    // Paper start - Fix NPE in SculkBloomEvent world access
++    @Override
++    public void setLevel(Level level) {
++        super.setLevel(level);
++        this.catalystListener.sculkSpreader.level = level;
++    }
++    // Paper end - Fix NPE in SculkBloomEvent world access
++
+     public static void serverTick(Level level, BlockPos pos, BlockState state, SculkCatalystBlockEntity sculkCatalyst) {
++        org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = sculkCatalyst.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
+         sculkCatalyst.catalystListener.getSculkSpreader().updateCursors(level, pos, level.getRandom(), true);
++        org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit
+     }
+ 
+     @Override
diff --git a/paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch b/paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
new file mode 100644
index 0000000000..09f5bd3726
--- /dev/null
+++ b/paper-server/patches/sources/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
@@ -0,0 +1,11 @@
+--- a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
++++ b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
+@@ -131,7 +_,7 @@
+ 
+     @Nullable
+     public Vec3 getPortalPosition(ServerLevel level, BlockPos pos) {
+-        if (this.exitPortal == null && level.dimension() == Level.END) {
++        if (this.exitPortal == null && level.getTypeKey() == net.minecraft.world.level.dimension.LevelStem.END) { // CraftBukkit - work in alternate worlds
+             BlockPos blockPos = findOrCreateValidTeleportPos(level, pos);
+             blockPos = blockPos.above(10);
+             LOGGER.debug("Creating portal at {}", blockPos);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
deleted file mode 100644
index 537f1913b2..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/ChestBlockEntity.java.patch
+++ /dev/null
@@ -1,51 +0,0 @@
---- a/net/minecraft/world/level/block/entity/ChestBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/ChestBlockEntity.java
-@@ -23,6 +23,11 @@
- import net.minecraft.world.level.block.ChestBlock;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.block.state.properties.ChestType;
-+// CraftBukkit start
-+import java.util.List;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.entity.HumanEntity;
-+// CraftBukkit end
- 
- public class ChestBlockEntity extends RandomizableContainerBlockEntity implements LidBlockEntity {
- 
-@@ -31,6 +36,36 @@
-     public final ContainerOpenersCounter openersCounter;
-     private final ChestLidController chestLidController;
- 
-+    // 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) {
-+        this.transaction.add(who);
-+    }
-+
-+    public void onClose(CraftHumanEntity who) {
-+        this.transaction.remove(who);
-+    }
-+
-+    public List<HumanEntity> getViewers() {
-+        return this.transaction;
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    public void setMaxStackSize(int size) {
-+        this.maxStack = size;
-+    }
-+    // CraftBukkit end
-+
-     protected ChestBlockEntity(BlockEntityType<?> type, BlockPos pos, BlockState state) {
-         super(type, pos, state);
-         this.items = NonNullList.withSize(27, ItemStack.EMPTY);
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
deleted file mode 100644
index 49a6bff68c..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/CommandBlockEntity.java.patch
+++ /dev/null
@@ -1,26 +0,0 @@
---- a/net/minecraft/world/level/block/entity/CommandBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/CommandBlockEntity.java
-@@ -24,7 +24,14 @@
-     private boolean auto;
-     private boolean conditionMet;
-     private final BaseCommandBlock commandBlock = new BaseCommandBlock() {
-+        // CraftBukkit start
-         @Override
-+        public org.bukkit.command.CommandSender getBukkitSender(CommandSourceStack wrapper) {
-+            return new org.bukkit.craftbukkit.command.CraftBlockCommandSender(wrapper, CommandBlockEntity.this);
-+        }
-+        // CraftBukkit end
-+
-+        @Override
-         public void setCommand(String command) {
-             super.setCommand(command);
-             CommandBlockEntity.this.setChanged();
-@@ -51,7 +58,7 @@
-         public CommandSourceStack createCommandSourceStack() {
-             Direction enumdirection = (Direction) CommandBlockEntity.this.getBlockState().getValue(CommandBlock.FACING);
- 
--            return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), 2, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null);
-+            return new CommandSourceStack(this, Vec3.atCenterOf(CommandBlockEntity.this.worldPosition), new Vec2(0.0F, enumdirection.toYRot()), this.getLevel(), this.getLevel().paperConfig().commandBlocks.permissionsLevel, this.getName().getString(), this.getName(), this.getLevel().getServer(), (Entity) null); // Paper - configurable command block perm level
-         }
- 
-         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
deleted file mode 100644
index 58e1ea39e6..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/HopperBlockEntity.java.patch
+++ /dev/null
@@ -1,322 +0,0 @@
---- a/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/HopperBlockEntity.java
-@@ -11,6 +11,7 @@
- import net.minecraft.nbt.CompoundTag;
- import net.minecraft.network.chat.Component;
- import net.minecraft.tags.BlockTags;
-+import net.minecraft.world.CompoundContainer;
- import net.minecraft.world.Container;
- import net.minecraft.world.ContainerHelper;
- import net.minecraft.world.WorldlyContainer;
-@@ -18,7 +19,6 @@
- import net.minecraft.world.entity.Entity;
- import net.minecraft.world.entity.EntitySelector;
- import net.minecraft.world.entity.item.ItemEntity;
--import net.minecraft.world.entity.player.Inventory;
- import net.minecraft.world.entity.player.Player;
- import net.minecraft.world.inventory.AbstractContainerMenu;
- import net.minecraft.world.inventory.HopperMenu;
-@@ -29,6 +29,18 @@
- import net.minecraft.world.level.block.HopperBlock;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.phys.AABB;
-+import org.bukkit.Bukkit;
-+import org.bukkit.craftbukkit.block.CraftBlock;
-+import org.bukkit.craftbukkit.entity.CraftHumanEntity;
-+import org.bukkit.craftbukkit.inventory.CraftInventory;
-+import org.bukkit.craftbukkit.inventory.CraftItemStack;
-+import org.bukkit.entity.HumanEntity;
-+import org.bukkit.event.entity.EntityRemoveEvent;
-+import org.bukkit.event.inventory.HopperInventorySearchEvent;
-+import org.bukkit.event.inventory.InventoryMoveItemEvent;
-+import org.bukkit.event.inventory.InventoryPickupItemEvent;
-+import org.bukkit.inventory.Inventory;
-+// CraftBukkit end
- 
- public class HopperBlockEntity extends RandomizableContainerBlockEntity implements Hopper {
- 
-@@ -40,6 +52,36 @@
-     private long tickedGameTime;
-     private Direction facing;
- 
-+    // 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) {
-+        this.transaction.add(who);
-+    }
-+
-+    public void onClose(CraftHumanEntity who) {
-+        this.transaction.remove(who);
-+    }
-+
-+    public List<HumanEntity> getViewers() {
-+        return this.transaction;
-+    }
-+
-+    @Override
-+    public int getMaxStackSize() {
-+        return this.maxStack;
-+    }
-+
-+    public void setMaxStackSize(int size) {
-+        this.maxStack = size;
-+    }
-+    // CraftBukkit end
-+
-     public HopperBlockEntity(BlockPos pos, BlockState state) {
-         super(BlockEntityType.HOPPER, pos, state);
-         this.items = NonNullList.withSize(5, ItemStack.EMPTY);
-@@ -102,9 +144,14 @@
-         blockEntity.tickedGameTime = world.getGameTime();
-         if (!blockEntity.isOnCooldown()) {
-             blockEntity.setCooldown(0);
--            HopperBlockEntity.tryMoveItems(world, pos, state, blockEntity, () -> {
-+            // Spigot start
-+            boolean result = HopperBlockEntity.tryMoveItems(world, pos, state, blockEntity, () -> {
-                 return HopperBlockEntity.suckInItems(world, blockEntity);
-             });
-+            if (!result && blockEntity.level.spigotConfig.hopperCheck > 1) {
-+                blockEntity.setCooldown(blockEntity.level.spigotConfig.hopperCheck);
-+            }
-+            // Spigot end
-         }
- 
-     }
-@@ -125,7 +172,7 @@
-                 }
- 
-                 if (flag) {
--                    blockEntity.setCooldown(8);
-+                    blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Spigot
-                     setChanged(world, pos, state);
-                     return true;
-                 }
-@@ -167,15 +214,41 @@
- 
-                     if (!itemstack.isEmpty()) {
-                         int j = itemstack.getCount();
--                        ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, blockEntity.removeItem(i, 1), enumdirection);
-+                        // CraftBukkit start - Call event when pushing items into other inventories
-+                        ItemStack original = itemstack.copy();
-+                        CraftItemStack oitemstack = CraftItemStack.asCraftMirror(blockEntity.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot
- 
-+                        Inventory destinationInventory;
-+                        // Have to special case large chests as they work oddly
-+                        if (iinventory instanceof CompoundContainer) {
-+                            destinationInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory);
-+                        } else if (iinventory.getOwner() != null) {
-+                            destinationInventory = iinventory.getOwner().getInventory();
-+                        } else {
-+                            destinationInventory = new CraftInventory(iinventory);
-+                        }
-+
-+                        InventoryMoveItemEvent event = new InventoryMoveItemEvent(blockEntity.getOwner().getInventory(), oitemstack, destinationInventory, true);
-+                        world.getCraftServer().getPluginManager().callEvent(event);
-+                        if (event.isCancelled()) {
-+                            blockEntity.setItem(i, original);
-+                            blockEntity.setCooldown(world.spigotConfig.hopperTransfer); // Delay hopper checks // Spigot
-+                            return false;
-+                        }
-+                        int origCount = event.getItem().getAmount(); // Spigot
-+                        ItemStack itemstack1 = HopperBlockEntity.addItem(blockEntity, iinventory, CraftItemStack.asNMSCopy(event.getItem()), enumdirection);
-+                        // CraftBukkit end
-+
-                         if (itemstack1.isEmpty()) {
-                             iinventory.setChanged();
-                             return true;
-                         }
- 
-                         itemstack.setCount(j);
--                        if (j == 1) {
-+                        // Spigot start
-+                        itemstack.shrink(origCount - itemstack1.getCount());
-+                        if (j <= world.spigotConfig.hopperAmount) {
-+                            // Spigot end
-                             blockEntity.setItem(i, itemstack);
-                         }
-                     }
-@@ -249,7 +322,7 @@
-             for (int j = 0; j < i; ++j) {
-                 int k = aint[j];
- 
--                if (HopperBlockEntity.tryTakeInItemFromSlot(hopper, iinventory, k, enumdirection)) {
-+                if (HopperBlockEntity.tryTakeInItemFromSlot(hopper, iinventory, k, enumdirection, world)) { // Spigot
-                     return true;
-                 }
-             }
-@@ -274,21 +347,52 @@
-         }
-     }
- 
--    private static boolean tryTakeInItemFromSlot(Hopper hopper, Container inventory, int slot, Direction side) {
--        ItemStack itemstack = inventory.getItem(slot);
-+    private static boolean tryTakeInItemFromSlot(Hopper ihopper, Container iinventory, int i, Direction enumdirection, Level world) { // Spigot
-+        ItemStack itemstack = iinventory.getItem(i);
- 
--        if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(hopper, inventory, itemstack, slot, side)) {
-+        if (!itemstack.isEmpty() && HopperBlockEntity.canTakeItemFromContainer(ihopper, iinventory, itemstack, i, enumdirection)) {
-             int j = itemstack.getCount();
--            ItemStack itemstack1 = HopperBlockEntity.addItem(inventory, hopper, inventory.removeItem(slot, 1), (Direction) null);
-+            // CraftBukkit start - Call event on collection of items from inventories into the hopper
-+            ItemStack original = itemstack.copy();
-+            CraftItemStack oitemstack = CraftItemStack.asCraftMirror(iinventory.removeItem(i, world.spigotConfig.hopperAmount)); // Spigot
- 
-+            Inventory sourceInventory;
-+            // Have to special case large chests as they work oddly
-+            if (iinventory instanceof CompoundContainer) {
-+                sourceInventory = new org.bukkit.craftbukkit.inventory.CraftInventoryDoubleChest((CompoundContainer) iinventory);
-+            } else if (iinventory.getOwner() != null) {
-+                sourceInventory = iinventory.getOwner().getInventory();
-+            } else {
-+                sourceInventory = new CraftInventory(iinventory);
-+            }
-+
-+            InventoryMoveItemEvent event = new InventoryMoveItemEvent(sourceInventory, oitemstack, ihopper.getOwner().getInventory(), false);
-+
-+            Bukkit.getServer().getPluginManager().callEvent(event);
-+            if (event.isCancelled()) {
-+                iinventory.setItem(i, original);
-+
-+                if (ihopper instanceof HopperBlockEntity) {
-+                    ((HopperBlockEntity) ihopper).setCooldown(world.spigotConfig.hopperTransfer); // Spigot
-+                }
-+
-+                return false;
-+            }
-+            int origCount = event.getItem().getAmount(); // Spigot
-+            ItemStack itemstack1 = HopperBlockEntity.addItem(iinventory, ihopper, CraftItemStack.asNMSCopy(event.getItem()), null);
-+            // CraftBukkit end
-+
-             if (itemstack1.isEmpty()) {
--                inventory.setChanged();
-+                iinventory.setChanged();
-                 return true;
-             }
- 
-             itemstack.setCount(j);
--            if (j == 1) {
--                inventory.setItem(slot, itemstack);
-+            // Spigot start
-+            itemstack.shrink(origCount - itemstack1.getCount());
-+            if (j <= world.spigotConfig.hopperAmount) {
-+                // Spigot end
-+                iinventory.setItem(i, itemstack);
-             }
-         }
- 
-@@ -297,13 +401,20 @@
- 
-     public static boolean addItem(Container inventory, ItemEntity itemEntity) {
-         boolean flag = false;
-+        // CraftBukkit start
-+        InventoryPickupItemEvent event = new InventoryPickupItemEvent(inventory.getOwner().getInventory(), (org.bukkit.entity.Item) itemEntity.getBukkitEntity());
-+        itemEntity.level().getCraftServer().getPluginManager().callEvent(event);
-+        if (event.isCancelled()) {
-+            return false;
-+        }
-+        // CraftBukkit end
-         ItemStack itemstack = itemEntity.getItem().copy();
-         ItemStack itemstack1 = HopperBlockEntity.addItem((Container) null, inventory, itemstack, (Direction) null);
- 
-         if (itemstack1.isEmpty()) {
-             flag = true;
-             itemEntity.setItem(ItemStack.EMPTY);
--            itemEntity.discard();
-+            itemEntity.discard(EntityRemoveEvent.Cause.PICKUP); // CraftBukkit - add Bukkit remove cause
-         } else {
-             itemEntity.setItem(itemstack1);
-         }
-@@ -383,11 +494,18 @@
-             boolean flag1 = to.isEmpty();
- 
-             if (itemstack1.isEmpty()) {
-+                // Spigot start - SPIGOT-6693, InventorySubcontainer#setItem
-+                ItemStack leftover = ItemStack.EMPTY; // Paper - Make hoppers respect inventory max stack size
-+                if (!stack.isEmpty() && stack.getCount() > to.getMaxStackSize()) {
-+                    leftover = stack; // Paper - Make hoppers respect inventory max stack size
-+                    stack = stack.split(to.getMaxStackSize());
-+                }
-+                // Spigot end
-                 to.setItem(slot, stack);
--                stack = ItemStack.EMPTY;
-+                stack = leftover; // Paper - Make hoppers respect inventory max stack size
-                 flag = true;
-             } else if (HopperBlockEntity.canMergeItems(itemstack1, stack)) {
--                int j = stack.getMaxStackSize() - itemstack1.getCount();
-+                int j = Math.min(stack.getMaxStackSize(), to.getMaxStackSize()) - itemstack1.getCount(); // Paper - Make hoppers respect inventory max stack size
-                 int k = Math.min(stack.getCount(), j);
- 
-                 stack.shrink(k);
-@@ -410,7 +528,7 @@
-                             }
-                         }
- 
--                        tileentityhopper.setCooldown(8 - b0);
-+                        tileentityhopper.setCooldown(tileentityhopper.level.spigotConfig.hopperTransfer - b0); // Spigot
-                     }
-                 }
- 
-@@ -421,14 +539,38 @@
-         return stack;
-     }
- 
-+    // CraftBukkit start
-     @Nullable
-+    private static Container runHopperInventorySearchEvent(Container inventory, CraftBlock hopper, CraftBlock searchLocation, HopperInventorySearchEvent.ContainerType containerType) {
-+        HopperInventorySearchEvent event = new HopperInventorySearchEvent((inventory != null) ? new CraftInventory(inventory) : null, containerType, hopper, searchLocation);
-+        Bukkit.getServer().getPluginManager().callEvent(event);
-+        CraftInventory craftInventory = (CraftInventory) event.getInventory();
-+        return (craftInventory != null) ? craftInventory.getInventory() : null;
-+    }
-+    // CraftBukkit end
-+
-+    @Nullable
-     private static Container getAttachedContainer(Level world, BlockPos pos, HopperBlockEntity blockEntity) {
--        return HopperBlockEntity.getContainerAt(world, pos.relative(blockEntity.facing));
-+        // CraftBukkit start
-+        BlockPos searchPosition = pos.relative(blockEntity.facing);
-+        Container inventory = HopperBlockEntity.getContainerAt(world, searchPosition);
-+
-+        CraftBlock hopper = CraftBlock.at(world, pos);
-+        CraftBlock searchBlock = CraftBlock.at(world, searchPosition);
-+        return HopperBlockEntity.runHopperInventorySearchEvent(inventory, hopper, searchBlock, HopperInventorySearchEvent.ContainerType.DESTINATION);
-+        // CraftBukkit end
-     }
- 
-     @Nullable
-     private static Container getSourceContainer(Level world, Hopper hopper, BlockPos pos, BlockState state) {
--        return HopperBlockEntity.getContainerAt(world, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ());
-+        // CraftBukkit start
-+        Container inventory = HopperBlockEntity.getContainerAt(world, pos, state, hopper.getLevelX(), hopper.getLevelY() + 1.0D, hopper.getLevelZ());
-+
-+        BlockPos blockPosition = BlockPos.containing(hopper.getLevelX(), hopper.getLevelY(), hopper.getLevelZ());
-+        CraftBlock hopper1 = CraftBlock.at(world, blockPosition);
-+        CraftBlock container = CraftBlock.at(world, blockPosition.above());
-+        return HopperBlockEntity.runHopperInventorySearchEvent(inventory, hopper1, container, HopperInventorySearchEvent.ContainerType.SOURCE);
-+        // CraftBukkit end
-     }
- 
-     public static List<ItemEntity> getItemsAtAndAbove(Level world, Hopper hopper) {
-@@ -455,6 +597,7 @@
- 
-     @Nullable
-     private static Container getBlockContainer(Level world, BlockPos pos, BlockState state) {
-+        if ( !world.spigotConfig.hopperCanLoadChunks && !world.hasChunkAt( pos ) ) return null; // Spigot
-         Block block = state.getBlock();
- 
-         if (block instanceof WorldlyContainerHolder) {
-@@ -543,7 +686,7 @@
-     }
- 
-     @Override
--    protected AbstractContainerMenu createMenu(int syncId, Inventory playerInventory) {
-+    protected AbstractContainerMenu createMenu(int syncId, net.minecraft.world.entity.player.Inventory playerInventory) {
-         return new HopperMenu(syncId, playerInventory, this);
-     }
- }
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
deleted file mode 100644
index 49d4011082..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java.patch
+++ /dev/null
@@ -1,29 +0,0 @@
---- a/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/SculkCatalystBlockEntity.java
-@@ -37,8 +37,18 @@
-         this.catalystListener = new SculkCatalystBlockEntity.CatalystListener(state, new BlockPositionSource(pos));
-     }
- 
-+    // Paper start - Fix NPE in SculkBloomEvent world access
-+    @Override
-+    public void setLevel(Level level) {
-+        super.setLevel(level);
-+        this.catalystListener.sculkSpreader.level = level;
-+    }
-+    // Paper end - Fix NPE in SculkBloomEvent world access
-+
-     public static void serverTick(Level world, BlockPos pos, BlockState state, SculkCatalystBlockEntity blockEntity) {
-+        org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = blockEntity.getBlockPos(); // CraftBukkit - SPIGOT-7068: Add source block override, not the most elegant way but better than passing down a BlockPosition up to five methods deep.
-         blockEntity.catalystListener.getSculkSpreader().updateCursors(world, pos, world.getRandom(), true);
-+        org.bukkit.craftbukkit.event.CraftEventFactory.sourceBlockOverride = null; // CraftBukkit
-     }
- 
-     @Override
-@@ -69,6 +79,7 @@
-             this.blockState = state;
-             this.positionSource = positionSource;
-             this.sculkSpreader = SculkSpreader.createLevelSpreader();
-+            // this.sculkSpreader.level = this.level; // CraftBukkit // Paper - Fix NPE in SculkBloomEvent world access
-         }
- 
-         @Override
diff --git a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch b/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
deleted file mode 100644
index b5587ac5d9..0000000000
--- a/paper-server/patches/unapplied/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java.patch
+++ /dev/null
@@ -1,19 +0,0 @@
---- a/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
-+++ b/net/minecraft/world/level/block/entity/TheEndGatewayBlockEntity.java
-@@ -21,6 +21,7 @@
- import net.minecraft.world.level.block.Blocks;
- import net.minecraft.world.level.block.state.BlockState;
- import net.minecraft.world.level.chunk.LevelChunk;
-+import net.minecraft.world.level.dimension.LevelStem;
- import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
- import net.minecraft.world.level.levelgen.feature.Feature;
- import net.minecraft.world.level.levelgen.feature.configurations.EndGatewayConfiguration;
-@@ -143,7 +144,7 @@
-     public Vec3 getPortalPosition(ServerLevel world, BlockPos pos) {
-         BlockPos blockposition1;
- 
--        if (this.exitPortal == null && world.dimension() == Level.END) {
-+        if (this.exitPortal == null && world.getTypeKey() == LevelStem.END) { // CraftBukkit - work in alternate worlds
-             blockposition1 = TheEndGatewayBlockEntity.findOrCreateValidTeleportPos(world, pos);
-             blockposition1 = blockposition1.above(10);
-             TheEndGatewayBlockEntity.LOGGER.debug("Creating portal at {}", blockposition1);